標籤:
一些朋友總是諮詢關於二進位檔案的讀寫和轉化。這裡就我自己的理解說一說。
一).一般問題 二進位檔案與我們通常使用的文字檔儲存方式有根本的不同。這樣的不同很難用言語表達,自己親自看一看,理解起來會容易得多。因此,我推薦學習二進位檔案讀寫的朋友安裝一款十六進位編輯器。這樣的編輯器有很多,在我們的 CVF 附帶的整合式開發環境下就可以(將二進位檔案拖動到 IDE 視窗後鬆開)。Visual Studio 2005 也是可以的。(不過需要在 File 菜單下 Open,File) 另外推薦一款使用較多的軟體,叫做 UltraEdit(以下簡稱 UE)。是很不錯的文字編輯器,也能做十六進位編輯器使用。 為什麼要用十六進位編輯器?而不用 2 進位呢?因為 2 進位實在太小,書寫起來會很長,很不直觀。而我們的電腦把 8 位作為一個位元組。剛好 2 ** 8 = 256 = 16 ** 2。用 8 位 2 進位表達的數,我們用 2 個十六進位資料來表達,更直觀和方便。
二).檔案格式 所有檔案,籠統意義上將可以區分為兩類,一類是文字檔,一類是二進位檔案。
1).文字檔 文字檔用記事本等文字編輯器開啟,我們可以看懂上面的資訊。所以使用比較廣泛。通常一個文字檔分為很多很多行,作為資料儲存時,還有列的概念。實際上,儲存在硬碟或其他介質上,檔案內容是線一樣儲存的,列是用空格或 Tab 間隔,行是用斷行符號和分行符號間隔。 以 ANSI 編碼(使用較多)的文字檔來說,例如我們儲存如下資訊: 10 11 12 需要的空間是:3 行 × 每行 2 個字元 + 2 個斷行符號符 + 2 個分行符號 = 10 位元組。文字檔儲存資料是有格式,無資料類型的。比如 10 這個資料,並不指定是整型還是實型還是字串。它有長度,就是 2,兩個位元組。儲存時電腦儲存它的 ASCII 碼:31h,30h。(十六進位表示)。斷行符號符是:0Dh,分行符號:0Ah。 因此,這個資料儲存是這樣的: 31 30 0D 0A 31 31 0D 0A 31 32 (紅色為斷行符號符和分行符號) 31h 30h 就是 10,31h 31h 就是 11,31h 32h 就是 12。因此我們也可以認為文字檔是特殊的二進位檔案。 2).二進位檔案 二進位檔案,是無格式有資料類型的。比如上面的 10 11 12 三個數。但二進位檔案沒有行的概念。我們要緊湊地儲存他們。(當然也可以中間加入一些空白的位元組) 從資料類型上來說,我們首先考慮整型。如果把 10 11 12 當作 2 字長的整型。則 10 表示為:0Ah 00h。因為 0Ah 對應十進位 10。而後面的 00h 是空白位。2 字長的整型如果不足 FFh,也就是不足 255,則需要一個空白位。類似的:11 表示為 0Bh 00h,12 表示為 0Ch 00h。 當整型資料超過 255 時,我們需要 2 個位元組來儲存。比如 2748(ABCh),則表示為:BCh 0Ah。要把低位寫在前面(BCh),高位寫在後面(0Ah)。 當整型資料超過 65535 時,我們就需要 4 個位元組來儲存。比如 439041101(1A2B3C4Dh),則表示成:4Dh 3Ch 2Bh 1Ah。當資料再大時,我們就需要 8 位元組儲存了。 二進位檔案的實型資料也有位元組長度的區分,比如 4 字長,8 字長。但實型資料的長度並不僅僅代表它的表達的範圍,更多的代表精度。所以,8 字長的我們又稱為雙精確度。關於實型資料如何儲存為 2 進位。則有很多套規則。現在都廣泛使用的是 IEEE 標準浮點格式。關於這樣的規則,我還正在瞭解,比較麻煩。就不多說了。在這裡也沒有必要瞭解。 二進位檔案也可以儲存字元型資料,儲存方法和文字檔一樣。都是使用 ASCII 編碼儲存的。所以我們用記事本開啟某些二進位檔案時,也能看到一些有意義的字串。(無意義的亂碼我們可以認為是整型或實型,不過記事本程式當作字元來解釋,因此造成了亂碼)
三).使用二進位檔案的好處 為什麼要使用二進位檔案。原因大概有三個: 第一是二進位檔案比較節約空間,這兩者儲存字元型資料時並沒有差別。但是在儲存數字,特別是實型數字時,二進位更節省空間的,比如儲存 Real*4 的資料:3.1415927,文字檔需要 9 個位元組,分別儲存:3 . 1 4 1 5 9 2 7 這 9 個 ASCII 值,而二進位檔案只需要 4 個位元組(DB 0F 49 40) 第二個原因是,記憶體中參加計算的資料都是用二進位無格式儲存起來的,因此,使用二進位儲存到檔案就更快捷。如果儲存為文字檔,則需要一個轉換的過程。在資料量很大的時候,兩者就會有明顯的速度差別了。 第三,就是一些比較精確的資料,使用二進位儲存不會造成有效位的丟失。
四).二進位檔案的儲存方式 列舉一個二進位檔案如下: 00000000h: 0F 01 00 00 0F 03 00 00 12 53 21 45 58 62 35 34 ; .........S!EXb54 00000010h: 41 42 43 44 45 46 47 48 49 47 4B 4C 4D 4E 4F 50 ; ABCDEFGHIGKLMNOP 這裡列出的是在 UltraEdit(UE) 裡看到的東西。其實只有紅色部分是檔案內容。前面的是 UE 加入的行號。後面的是 UE 嘗試解釋為字元型的參考。 這個檔案一共有 32 位元組長。顯示為兩列,每列 16 個位元組。實際上,這僅僅是 UE 的顯示而已。真實的檔案並不分行。僅僅知道這個檔案的內容,如果我們沒有任何說明的話,是不能看出任何有用資訊的。 下面我規定一下說明:我們認為,前 4 個位元組是一個 4 位元組的整型資料(0F 01 00 00 十六進位:10Fh 十進位:271)。這 4 個位元組之後的 4 個位元組是另一個 4 位元組的整型資料(0F 03 00 00 十六進位:30Fh 十進位:783)。其後的 4 個位元組(12 53 21 45 )表示一個 4 位元組的實型資料:2.5811919E+3。再其後的 4 個位元組(58 62 35 34)表示另一個 4 位元組的實行資料:1.6892716E-7。而只後的 16 個位元組(41 42 43 44 45 46 47 48 49 47 4B 4C 4D 4E 4F 50)我們認為是 16 個位元組的字串(ABCDEFGHIGKLMNOP) 實際上,二進位檔案只是儲存資料,並不寫明資料類型,比如上面的第 9 位元組到第 16 位元組(12 53 21 45 58 62 35 34),我們剛才認為是 2 個 4 位元組的實型,其實也可以認為是 8 個位元組的字元型( S!EXb54)。而後面的 16 個位元組的字串(ABCDEFGHIGKLMNOP),我們也可以認為是 2 個 8 位元組的整型,或者 4 個 4 位元組的整型,甚至 2 個 8 位元組的實型,4 個 4 位元組的實型,等等等等。 因此,面對一個二進位檔案,我們不能準確地知道它的含義,我們需要他的資料儲存方式的說明。這個說明告訴我們第幾個位元組到第幾個位元組是什麼類型的資料,儲存的資料是什麼含義。否則的話,我們只能猜測,或者無能為力。
五).如何使用語句操作二進位檔案 我們將上面的那個二進位檔案儲存為:TestBin.Bin 來舉例。 讀取和寫入二進位其實是兩個很類似的操作,瞭解了其中之一,另一個也就不難了。 二進位檔案我們通常使用直接讀取方式,Open 語句可以寫為: Open( 12 , File = ‘TestBin.Bin‘ , Access = ‘Direct‘ , Form = ‘Unformatted‘ , RecL = 4 ) 上面的 Access 表示直接讀取方式,Form 表示無格式儲存。比較重要的是 RecL 。我們讀取資料時,是用記錄來描述單位的,每一次讀入或寫入是一個記錄。記錄的長度在 Open 時就確定下來,以後不能改變。如果需要改變,只能 Close 以後再此 Open。 記錄長度在某些編譯器下表示讀取的 4 位元組長度的倍數,規定為 4 表示記錄長度為 16 位元組。有些編譯器下就直接表示記錄的位元組數,規定為 4 則表示記錄長度為 4 位元組。這個問題需要參考編譯器手冊。在 VF 系列裡,這個值是前面一個含義。可以通過設定工程屬性的 Fortran,Data,Use Bytes as RECL= Unit for Unformatted Files 來改變,使之成為後一個含義。在命令列模式下,則使用 /assume:byterecl 這個編譯選項。 確定 RecL 大小是我們需要做的事情,一般來說,不適合太大,也不適合太小。還需要結合資料儲存方式來考慮。太小的話,我們需要執行讀寫的次數就多,太大的話,我們就不方便操作小範圍的資料。 有時候我們甚至會分多次來讀取資料,每一次的 RecL 都不同。對於上面的 TestBin.Bin 檔案來說,它比較簡單,我以 16 位元組長度和 8 位元組長度兩種讀取方式來示範,你甚至可以一次 32 個位元組長度全部讀完: (1)RecL = 4 【記錄長度 16 位元組】
Program www_fcode_cn Implicit None Integer*4 :: iVar1 , iVar2 Real*4 :: rVar1 , rVar2 Character(Len=16) :: cStr Open( 12 , File = ‘TestBin.Bin‘ , Access = ‘Direct‘ , Form = ‘Unformatted‘ , RecL = 4 ) Read( 12 , Rec = 2 ) cStr Read( 12 , Rec = 1 ) iVar1 , iVar2 , rVar1 , rVar2 Write( * , * ) cStr Write( * , * ) iVar1 , iVar2 , rVar1 , rVar2 Close( 12 )End Program www_fcode_cn
這裡的 Open 裡指定了 RecL = 4(記錄長度是 16 位元組)。 第一個 Read 語句,直接讀取第二筆記錄(也就是第 17 位元組到第 32 位元組)。讀取出的 cStr = "ABCDEFGHIGKLMNOP"。 第二個 Read 語句,返回來讀取第一筆記錄(也就是前面 16 個位元組)。讀取出的資料分別放入 4 個 4 位元組的變數。(其中前面兩個是整型,後面兩個是實型) 輸出結果為: ABCDEFGHIGKLMNOP 271 783 2581.192 1.6892716E-07 看到這個結果,就說明我們成功了。 同時我們可以看到,第一個語句,我們直接跳到第二條記錄讀取,並沒有讀取第一條。這就是直接讀取資料的方便。有時候我們根本不需要某些資料,這時候,我們可以直接跳到某一條記錄上。這個記錄甚至可以是我們實現算出來的變數。比如: iRec = ( a + b ) / C Read( 12 , Rec = iRec ) cStr 實現我們儲存了 100 天的資料,我們只需要第 21 天的資料,我們怎麼辦?在順序讀取時,我們可能會開闢一個 100 元素的數組,或者迴圈執行 20 次空白的讀取。但是在直接讀取時,我們只需要執行一句 Read( 12 , Rec = 21 )。這是多麼的方便。(直接讀取和順序讀取雖然於文字檔和二進位檔案沒有直接的關聯,但是文字檔通常用順序讀取,而二進位檔案通常用直接讀取。這是他們的性質決定的。) (2)RecL = 2【記錄長度為 8 位元組】
Program www_fcode_cn Implicit None Integer*4 :: iVar1 , iVar2 Real*4 :: rVar1 , rVar2 Character(Len=16) :: cStr Open( 12 , File = ‘TestBin.Bin‘ , Access = ‘Direct‘ , Form = ‘Unformatted‘ , RecL = 2 ) Read( 12 , Rec = 4 ) cStr( 9 : 16 ) Read( 12 , Rec = 3 ) cStr( 1 : 8 ) Read( 12 , Rec = 1 ) iVar1 , iVar2 Read( 12 , Rec = 2 ) rVar1 , rVar2 Write( * , * ) cStr Write( * , * ) iVar1 , iVar2 , rVar1 , rVar2 Close( 12 )End Program www_fcode_cn
這裡設定的 RecL = 2 ,意思是一筆記錄 8 個位元組。所以我們不能一次讀取 cStr 這個 16 位元組的字串。我們必須分兩次讀取。第一次讀取第 4 筆記錄,放入字串後半段。第二次讀取第 3 筆記錄,放入字串前半段。(可以調換位置)。然後讀取第一筆記錄的兩個整型變數和第二筆記錄的兩個實型變數。 輸出結果和(1)的方法一樣。 (3)寫入二進位檔案 寫入二進位檔案同樣需要考慮 RecL 的問題。我們這裡以 RecL = 4 來舉例。
Program www_fcode_cn Implicit None Open( 12 , File = ‘TestBin.Bin‘ , Access = ‘Direct‘ , Form = ‘Unformatted‘ , RecL = 4 ) Write( 12 , Rec = 1 ) 271 , 783 , 2581.192_4 , 1.6892716E-07 Write( 12 , Rec = 2 ) "ABCDEFGHIGKLMNOP" Close( 12 )End Program www_fcode_cn
寫入二進位檔案和讀取二進位檔案是差不多的,我就不再解釋了。需要注意的是,如果直接寫入第 N 筆記錄,而檔案沒有只有 M 筆記錄(M < N),那麼,第 M+1 到第 N-1 筆記錄會用 0 填充。也就是說,二進位檔案不會出現斷裂。
二進位檔案的讀寫是比較靈活的,實際應用中,我們使用哪種方式,我們應該根據自己的情況來設計。如何選擇合適的記錄長度 RecL,如何設計高效的儲存方式等。
轉自:http://fcode.cn/content-10-4-1.html
[轉載:]Fortran 二進位檔案讀寫