第十四章 I/O與資料流處理
本章學習目標: 介紹C++ 提供的fstream、ifstream和ofstream類別。 利用open()函數來開啟檔案,配合不同的開啟模式能有不同的檔案處理。 介紹.NET Framework處理資料流的概念。 在CLR中認識和處理檔案有關的File和Directory類別。 說明處理資料流時,寫入資料的StreamWriter類別,讀取資料的StreamReader類別 在C++語言中,處理主制台應用程式是以cin來讀取使用者的輸入,並以cout輸出結果!本章節中,會分成二個部份來討論:第一個部份是傳統C++使用的資料流,並探討資料流和檔案之間的關聯性;另一個部份則是介紹.NET Framework使用的資料流和檔案
章節目錄 14-1 認識資料流 14-2 檔案管理 14-3 CLR的IO處理 14-2-1 開啟檔案 14-2-2 檔案的讀取 14-3-1 System名稱空間 14-3-2 使用標準資料流 14-3-3 檔案的輸入/輸出 14-2 檔案管理 14-2-1 開啟檔案 14-2-2 檔案的讀取 14-1 認識資料流 14-2-3 二進位檔案
章節目錄 14-1 認識資料流 14-2 檔案管理 14-3 CLR的IO處理 14-2-1 開啟檔案 14-2-2 檔案的讀取 14-3-1 System名稱空間 14-3-2 使用標準資料流 14-3-3 檔案的輸入/輸出 14-2 檔案管理 14-2-1 開啟檔案 14-2-2 檔案的讀取 14-2-3 二進位檔案
14-1 認識資料流 如何讓資料寫入檔案或讀取檔案內容,與這些程序息息相關的就是資料流。 尚未探討檔案之前,先瞭解什麼是資料流? 前面章節的範例中都是很單純地透過命令字元提示視窗輸入資料,利用cin物件來讀取,要輸出程式結果時就以cout物件顯示於螢幕上。 C++把資料流概分兩種: 一種是「輸出資料流」,把資料傳到輸出裝置(例如:螢幕、磁碟等)上; 另一種是「輸入資料流」,透過輸入裝置(例如:鍵盤、磁碟等將資料讀取。 事實上,先前章節我們幾乎都是使用輸出資料流將程式結果輸出到電腦螢幕上;利用cin物件來讀入資料,透過cout物件來輸出結果。
章節目錄 14-1 認識資料流 14-2 檔案管理
14-2 檔案管理 有資料流基本概念後,讓我們來看看檔案和資料流兩者之間的關係為何? 應用資料流的原理,可以讓我們存取磁碟中的檔案。 有了檔案處理程序,才能把辛苦建立的資料存在檔案中 讓程式執行時,直接到檔案裡讀取所需的資料內容 這樣的作法能讓我們將資料長久的保存下來(除非磁碟中存放資料的那個檔案被刪除),底下我們將分別介紹C++各種檔案處理常用的指令。
章節目錄 14-1 認識資料流 14-2-1 開啟檔案 14-2 檔案管理
14-2-1 開啟檔案 進行檔案操作時,C++提供三個類別
檔案輸入/輸出的類別 這些處理檔案輸入/輸出的基底類別是ios。從ios衍生出istream和ostream類別,istream類別又衍生出ifstream類別;ostream類別則衍生出ofstream類別;而fstream類別則是istream和ostream類別兩個共同衍生。
開啟檔案的語法 開啟檔案的語法如下: 開啟檔案時,必須先建立檔案物件,確認檔案開啟的模式,再執行開啟檔案的動作
建立檔案物件 ifstream ofstream fstream 建立檔案輸入物件 檔案物件有三種 建立檔案輸出物件 用來建立檔案的輸入/輸出物件
檔案開啟模式 檔案物件必須配合開啟模式,常見的開啟模式見下表所示: 開啟模式 說明 ios :: io ios :: out 開啟檔案後執行讀取動作 ios :: out 開啟檔案後執行寫入動作 ios :: app 開啟檔案後以附加方式將資料加到檔案最末端 ios :: trunc 開啟檔案後覆蓋原有檔案內容 ios :: binary 以二進位方式來開啟檔案 ios :: ate 開啟檔案後,將檔案指標移向檔案結尾
檔案物件的open函數原型
檔案的開啟與關閉 檔案是否開啟成功 關閉檔案 完成檔案的開啟後,必須把檔案關閉,語法如下: 我們利用以下範例「OutputFile.vcproj」來說明檔案的建立
範例:OutputFile\OutputFile.cpp
執行程式:OutputFile.cpp
程式解說:OutputFile.cpp 建立檔案必須在第2行引入標頭檔fstream。 第6行利用ofstream建立檔案輸出物件note;第8行利用open()函數來開啟此檔案物件,準備執行檔案的寫入動作。 第9~15行利用if/else敘述來判斷檔案是否建立成功,如果沒有開啟成功,第10行顯示訊息,如果開啟成功,在第13、14行把資料寫入sample.txt檔案,第16行則以close()函數來關閉檔案物件。 執行程式後,在與程式碼相同的資料夾之下找到「sample.txt」檔案,並用記事本來開啟,可看到檔案內有寫入資料!
附加至檔案結尾 完成檔案的建立後,我們要把資料寫入檔案 利用Append的觀念,讓寫入的資料附加到「sample.txt」原有內容的末端。
範例:AppendFile.cpp(1)
範例:AppendFile.cpp(2)
執行程式:AppendFile.cpp
程式解說:AppendFile.cpp 延續上一個範例架構,第7行利用ofstream建立檔案輸出物件note;第10行利用open()函數來開啟此檔案物件,檔案的開啟模式是以附加方式將資料寫到檔案的末端。 第16~22行利用do…while迴圈來詢問是否要繼續輸入資料,第19行則以檔案物件note配合「<<」運算子將資料寫入sample2.txt檔案內。 開啟sample2.txt可發現原來只有一筆記錄,執行程式時輸入的資料已經附加到原有記錄的末端。
章節目錄 14-1 認識資料流 14-2-1 開啟檔案 14-2 檔案管理 14-2-2 檔案的讀取
14-2-2 檔案的讀取 當我們要開啟一個檔案時,系統會把檔案內容讀到暫存區,讀取完畢時才會顯示螢幕上。 所以撰寫一個讀取檔案內容的程式 第一個動作要建立一個記憶體的緩衝區 再來必須瞭解是否讀完檔案內容! 如何才能知道是否已讀完內容呢? 通常是以檔案結尾來判斷,再把緩衝區的資料顯示於螢幕上。語法如下:
範例:InputFile\InputFile.cpp
執行程式:InputFile.cpp 第7行利用ifstream建立檔案輸入物件infpt,第8行宣告一個可以存放資料的緩衝區buff;第10行利用open()函數來開啟此檔案物件,檔案的開啟模式是讀取檔案內容 第11~18行利用if/else敘述配合fail()函數來判斷檔案是否開啟成功,如果沒有開啟成功,第12顯示無法開啟訊息 第15~17行利用while迴圈配合get()函數來讀取緩衝區的資料,第16行則以「<<」運算子將檔案資料輸出於螢幕上
判斷檔案結尾 範例「InputFile.cpp」是利用get()函數來讀取整個緩衝區的資料。 當資料內容較多時,可利用eof()函數來判斷是否已經讀到檔案末端。 不過,讀取資料方式就不能使用get()函數,必須利用getline()來一行一行讀取檔案內容。語法如下:
範例:InputFile\EndOfFile.cpp(1)
範例:InputFile\EndOfFile.cpp(2)
執行程式:EndOfFile.cpp
程式解說:EndOfFile.cpp 延續上一個範例架構,第7行利用ifstream建立檔案輸入物件infpt,第8行宣告一個可以存放資料的一維陣列緩衝區buff;第11行利用open()函數來開啟此檔案物件,檔案的開啟模式是讀取檔案內容。 第12~22行利用if/else敘述配合fail()函數來判斷檔案是否開啟成功,如果沒有開啟成功,第13行顯示無法開啟訊息。 第16~21行透過while迴圈配合eof()函數來判斷是否為檔案結尾!如果不是,利用getline()函數來讀取緩衝區的資料,第19行則以「<<」運算子將檔案資料輸出於螢幕上。
章節目錄 14-1 認識資料流 14-2-1 開啟檔案 14-2 檔案管理 14-2-2 檔案的讀取 14-2-3 二進位檔案
14-2-3 二進位檔案 我們都知道檔案分為二種: 二進位檔採用非格式化方式,語法如下: 文字檔 二進位檔 利用write()函數來寫入資料,透過「緩衝區」來存放欲寫入的資料,資料長度通常等於緩衝區的大小
範例:BinaryFile.cpp(1)
範例:BinaryFile.cpp(2)
執行程式:BinaryFile.cpp
程式解說:BinaryFile.cpp 第6~13行建立一個Student類別,利用第15~18行定義的成員函數display()來將資料的輸入緩衝區。 第23行利用ofstream來建立檔案輸出物件outfpt,第25行利用open()函數來建立二進位檔案sample.dat。 第26~38行利用if/else敘述先判斷檔案是否能夠開啟!檔案開啟後,再利用第28~34行do…while迴圈來繼續資料的輸入,第29行利用pupil物件來呼叫成員函數display()來輸入資料,第31行則是把緩衝區的資料利用write()函數寫入sample.dat檔案內,然後再第40行呼叫close()來關閉檔案。
讀取二進位檔案 處理二進位檔案,必須利用write()函數寫入資料 要讀取檔案內容可透過read()函數,語法如下: 要讀取一個二進位檔案,必須是一個已經開啟的二進位檔 可利用read()函數來讀取資料,透過「緩衝區」來存放欲讀取的資料,資料長度通常等於緩衝區的大小,如範例「ReadBinary.vcproj」所示。
範例:ReadBinary.vcproj(1)
範例:ReadBinary.vcproj(2)
執行程式:ReadBinary.cpp 第6~12行建立一個Student類別,利用第14~16行定義的成員函數display()將緩衝區的資料顯示於畫面上。 第20行利用ifstream來建立檔案輸入物件infpt,第22行利用open()函數來讀取二進位檔案sample2.dat。 第24~35行利用if/else敘述先判斷檔案是否能夠開啟!檔案開啟後,再利用第30行read()函數將資料讀取到緩衝區,透過while迴圈的eof()函數來判斷是否為檔案結尾,第32行利用pupil物件去呼叫成員函數display()來輸出緩衝區的資料,然後第34行read()函數再將資料讀到緩衝區到檔案結尾,最後第37行呼叫close()來關閉檔案。
章節目錄 14-1 認識資料流 14-2 檔案管理 14-3 CLR的IO處理 14-2-1 開啟檔案 14-2-2 檔案的讀取 14-2-3 二進位檔案 14-3 CLR的IO處理
14-3 CLR的IO處理 .NET Framework把每個檔案視為位元組的「循序資料流」(Stream) 每個檔案結尾是檔尾標記(end-of-file marker) 或是系統管理資料結構的特殊位元組標記,用來提供資料的來源和存放。
章節目錄 14-1 認識資料流 14-2 檔案管理 14-3 CLR的IO處理 14-2-1 開啟檔案 14-2-2 檔案的讀取 14-2-3 二進位檔案 14-3-1 System名稱空間 14-3 CLR的IO處理
14-3-1 System名稱空間 在.NET Framework架構中,要建立主控台應用程式(Console Application)必須使用繼承於System名稱空間的Console類別;其中資料成員 「In」用來取得標準輸入資料流 「Out」處理輸出資料流 和「Error」處理錯誤資料流
與主控台建立關聯的資料流物件 當我們在「共通語言執行環境」(CLR)中,啟動一個主控台應用程式時,作業系統會自動把資料流物件和主控台建立關聯,產生Console::out、Console::In和Console::Error的存取,說明如下: Console :: In 傳回標準輸入資料流物件,讓程式可以取得鍵盤輸入的資料 Console :: Out 傳回標準輸出資料流物件,讓程式可以輸出資料到螢幕 Console :: Error 傳回標準錯誤資料流物件,讓程式可以透過螢幕輸出錯誤訊息
使用Write()和WriteLine()函式 因此,我們是透過CLR來建立主控台應用程式,必須加入System名稱空間的宣告,然後利用Console類別的Write()和WriteLine()函式將文字顯示在螢幕上,敘述如下: 利用Read()函式來讀取字元,ReadLine()來讀取整行。我們利用一個簡單範例來認識CLR的輸入和輸出。
專案類型:CLR 範例:SimpleIO.vcproj
執行程式:SimpleIO.vcproj 使用的名稱空間為System,它是.NET Framework中基礎型別的根(Root)命名空間,代表所有應用程式使用的基底資料型別,包含:Object、Byte、Char、Array、Int32、String等等。 第5行宣告兩個字串:first和second,在CLR環境中,宣告時必須在字串前加上「^」符號。 第8、10行利用Write()函數將字串顯示於螢幕上,第9、11行則是利用ReadLine()函數來讀取輸入的字串並儲存於first和second字串中。 第12、13行將first、second字串利用Parse()函數轉換為Int32整數,然後把輸出結果利用WriteLine()函數輸出於螢幕上並做換行動作。
章節目錄 14-1 認識資料流 14-2 檔案管理 14-3 CLR的IO處理 14-2-1 開啟檔案 14-2-2 檔案的讀取 14-2-3 二進位檔案 14-3-1 System名稱空間 14-3 CLR的IO處理 14-3-2 使用標準資料流
14-3-2 使用標準資料流 從範例「Simple.vcproj」中,我們大致上已經瞭解System名稱空間和Console類別的用法。 在處理檔案I/O時還會使用另一個名稱空間「System.IO」,它允許從檔案讀取資料或者將資料寫入檔案和資料流使用的型別,並且提供檔案和目錄基本型別的支援。 在System.IO下的Stream類別是所有資料流的抽象基底類別 Stream類別和它的衍生類別提供不同型別輸入和輸出
資料流讀取/寫入的類別 類別名稱 用途 BinaryReader 以二進位方式來讀取Stream類別和基本資料型別 BinaryWriter StreamReader 以自訂位元組資料流方式來讀取TextReader的字元 StreamWriter 以自訂位元組資料流方式將字元寫入TextWriter StringReader 讀取TextReader實作的字串 StringWriter 將實作的字串寫入TextWriter TextReader 為StreamReader和StringReader的抽象基底類別,輸出Unicode字元 TextWriter 為StreamWriter和StringWriter的抽象基底類別,輸入Unicode字元 表14-2
TextReader和TextWriter類別 TextReader是StreamReader和StringReader的抽象基底類別 可用來讀取資料流和字串 衍生類別能用來開啟文字檔,以讀取指定範圍的字元,或根據現有資料流建立讀取器。 TextWriter則是StreamWriter和StringWriter的抽象基底類別 用來將字元寫入資料流和字串 建立TextWriter的實體物件時,能將物件寫入字串、將字串寫入檔案,或將XML序列化。
處理檔案輸入/輸出的類別 類別名稱 用途 Directory 進行一般作業,例如複製、移動、重新命名、建立和刪除目錄 表14-3 類別名稱 用途 Directory 進行一般作業,例如複製、移動、重新命名、建立和刪除目錄 DerectoryInfo 提供建立、移動目錄和子目錄的執行個體方法 DriveInfo 提供與磁碟機有關的建立方法 File 提供建立、複製、刪除、移動和開啟檔案的靜態方法 FileInfo 提供建立、複製、刪除、移動和開啟檔案的實體化方法 FileStream 可使用同步和非同步來開啟檔案,利用Seek方法來隨機存取檔案 FileSystemInfo 為FileInfo和DirectoryInfo的抽象基底類別 Path 提供處理目錄字串的方法和屬性 DeflateStream 透過Deflate演算法來壓縮和解壓縮資料流 GZipStream 提供方法和屬性來壓縮和解壓縮資料流 SerialPort 提供了方法和屬性來控制序列埠檔案資源
File類別 因為File類別只有靜態方法,所以不能利用File類別來實體化物件。 下表14-4為File類別常用的方法
File類別常用的方法 類別名稱 用途 AppendText 將指定字串附加至檔案,如果檔案不存在則建立一個檔案 Copy 表14-4 類別名稱 用途 AppendText 將指定字串附加至檔案,如果檔案不存在則建立一個檔案 Copy 複製現有檔案到新的檔案 Create 在指定的路徑建立檔案 CreateText 建立或開啟編碼方式為UTF-8的文字檔案 Delete 刪除指定的檔案 Exists 判斷檔案是否存在,存在回傳true,不存在則回傳false GetCreationTime DateTime物件,回傳檔案產生的時間 Move 移動檔案到指定位置 Open 利用FileStream來開啟指定的檔案 OpenRead 讀取開啟的檔案 OpenText 讀取已開啟的UTF-8編碼文字檔 OpenWrite 寫入開啟的檔案
Directory類別常用的方法 Directory類別提供目錄處理的能力 類別名稱 用途 CreateDirectory 表14-5 類別名稱 用途 CreateDirectory 產生一個目錄並以DirectoryInfo回傳相關訊息 Delete 刪除指定的目錄 Exists 判斷目錄是否存在,存在回傳true,不存在則回傳false GetDirectories 取得指定目錄中子目錄的名稱 GetFiles 取得指定目錄的檔案名稱 GetFileSystemEntries 取得指定目錄中所有子目錄和檔案名稱 Move 移動目錄和檔案到指定位置 SetCurrentDirectory 將應用程式的工作目錄指定為目前的目錄 SetLastWriteTime 設定目錄上次被寫入的日期和時間
章節目錄 14-1 認識資料流 14-2 檔案管理 14-3 CLR的IO處理 14-2-1 開啟檔案 14-2-2 檔案的讀取 14-2-3 二進位檔案 14-3-1 System名稱空間 14-3 CLR的IO處理 14-3-2 使用標準資料流 14-3-3 檔案的輸入/輸出
14-3-3 檔案的輸入/輸出 在Windows系統中是以分隔字元(separator character)「\」來分隔目錄和檔案路徑。 但是若以字串指定路徑時,必須要使用逸出反斜線字元,若要在D碟的「範例」目錄,建立一個「CLRsample.txt」檔案,就會形成「D:\\範例\\CLRsample.txt」撰寫方式。 建立一個檔案 在C++是使用open()函數並指定開啟模式 CLR則以StreamWriter來建立輸入檔案物件,語法如下 使用靜態函數CreateText()來建立或開啟檔案,參數path指的是要建立或要開啟的檔案。如果檔案不存在,會建立一個新的檔案;如果檔案已經存在會覆寫原有的檔案內容。我們透過下面的簡易範例來說明。
範例:CreateFile.vcproj
執行程式:CreateFile.cpp 執行程式後,直接開啟文字檔來檢視是否有把資料寫入檔案內 第6行利用字串來建立檔案路徑,所以必須加入逸出反斜線字元來讓編譯器進行剖析!如果沒有使用會造成編譯錯誤! 第7~19行為if條件判斷,直接利用File類別的靜態函數Exists()來判斷檔案是否存在!如果檔案不存在,就建立檔案。 如何建立檔案?在第9行敘述中,還是以File類別的靜態函數CreateText()來建立一個編碼方式為UTF-8的文字檔案,然後指派給StreamWriter物件note來處理所有Unicode字元。 第10~18行是一個try…finally敘述,把資料寫入檔案時,透過try敘述來捕捉是否發生錯誤,如果發生錯誤,finally敘述會把檔案寫入之後再呼叫例外處理常式來處理,並進行記憶體釋放
例外處理的finally 一般來說,發生例外狀況時,就會停止程式的執行,然後把控制權交給最接近例外處理的常式。 進行例外處理時,無論有無發生例外情形,try…finally敘述中的finally敘述一定會被執行,這可以讓未完成的動作持續到完成,因此可針對特殊情形來使用finally敘述,例如讀取檔案,進行資料庫連線等。
以StreamReader讀取檔案 我們已經知道要利用StreamWriter來建立檔案輸入物件,然後藉助File類別的靜態函數將資料寫入檔案中 如果要讀取檔案內的資料,當然得利用StreamReader來產生檔案輸出物件
範例:SystemIO.vcproj(1)
範例:SystemIO.vcproj(2)
執行程式:SystemIO.vcproj
程式解說:SystemIO.vcproj(1) 第一次執行時,故意把第7行的檔案名稱改為「SystemIO2.txt」,因此執行時就會透過第21行的catch敘述中的Message來顯示錯誤訊息。 第二次執行時,是一個正確的檔案名稱「SystemIO.txt」,所以就能把檔案內容讀出。 在此專案目錄下先建立一個有檔案內容的文字檔「SystemIO.txt」,再利用StreamReader讀出檔案內容而顯示於螢幕上。 第7行利用gcnew運算子來建立StreamReader類別的實體物件note。 第5~25行利用try…catch敘述進行第一層的例外處理;預防檔案在讀取時產生錯誤,如果找不到檔案,利用第22~25行的catch敘述來顯示錯誤訊息。由於開啟的是一個應用程式,所以利用System名稱空間的Exception類別來處理例外情形,透過資料成員Message來顯示錯誤訊息。
程式解說:SystemIO.vcproj(2) 第9~20行則是進行第二層的例外處理,利用try…finally敘述來讀取檔案是否有產生錯誤!如果發生例外情形,也能透過finally敘述把檔案內容讀完。
重點整理. 檔案物件有三種: 以ofstream類別建立輸出檔案物件方式: ifstream類別建立輸入檔案物件方式: 利用open()函數指定檔案開啟模式為「out」 再來是判斷檔案是否開啟成功 如果檔案順利開啟,則寫入資料,然後以close()函數關閉檔案 ifstream類別建立輸入檔案物件方式: 利用open()函數指定檔案開啟模式為「in」 然後是判斷檔案是否開啟成功 如果檔案順利開啟,以get()函數讀取資料,然後以close()函數關閉檔案。
重點整理.. 在CLR環境中,若要撰寫主控台應用程式,必須使用繼承於System名稱空間的Console類別 資料成員In用來取得標準輸入資料流 Out處理輸出資料流 Error處理錯誤資料流 在System.IO名稱空間下的Stream類別是所有資料流的抽象基底類別 Stream類別和它的衍生類別提供不同型別輸入和輸出 處理檔案的輸入/輸出時,必須利用StreamWriter類別來寫入資料 以StreamReader來讀取資料 File類別只提供靜態函數