用 C 讀取二進位制檔案

2021-04-29 21:21:28 字數 3547 閱讀 6327

當想到所有檔案都轉換為 xml時,確實是一件好事。但是,這並非事實。仍舊還有大量的檔案格式不是xml,甚至也不是ascii。二進位制檔案仍然在網路中傳播,儲存在磁碟上,在應用程式之間傳遞。相比之下,在處理這些問題方面,它們比文字檔案顯得更有效率些。

在 c 和 c++ 中,讀取二進位制檔案還是很容易的。除了一些開始符(carriage return)和結束符(line feed)的問題,每乙個讀到c/c++中的檔案都是二進位制檔案。事實上,c/c++ 只知道二進位制檔案,以及如何讓二進位制檔案像文字檔案一樣。當我們使用的語言越來越抽象時,我們最後使用的語言就不能直接、容易的讀取建立的檔案了。這些語言想要用它們自己獨特的方式來自動處理輸出資料。

問題的所在

在許多電腦科學領域,c 和 c++ 仍舊直接依照資料結構來儲存和讀取資料。在c和c++中,依照記憶體中的資料結構來讀取和寫檔案,是十分簡單的。在c中,你只需要使用fwrite()函式,並提供下列引數:乙個指向你的資料的指標,告訴它有多少個資料,乙個資料有多大。這樣,就直接用二進位制格式把資料寫成檔案了。

如上所述的那樣把資料寫成檔案,同時如果你也知道其正確的資料結構的話,那麼也就意味著讀取檔案也很容易。你只要使用 fread() 函式,並提供下列引數:乙個檔案控制代碼,乙個指向資料的指標,讀取多少個資料,每乙個資料的長度。 fread() 函式幫你把其餘的事都做了。突然,資料又回到了記憶體中。沒有採用解析以及也沒有物件模型的方式,它只是把檔案直接的讀到記憶體中。

資料對齊

因為處理器能夠一次處理更多的資訊(在乙個時鐘週期內),所以它們希望它們所處理的資訊能以一種確定的方式排列。大多數的 intel 處理器使整數型別(32位的)的儲存首位址能被4除盡(即:從能被4除盡的位址上開始儲存)。如果記憶體中的整數不是儲存在4的倍數的位址上的話,它們是不會工作的。編譯器知道這些。因此當編譯器遇到乙個可能引起這種問題的資料時,它們就有下面三種選擇。

第一種,它們可以選擇在資料中新增一些無用的白空格符,這樣可以使整數的開始位址能被4除盡。這是一種最普遍的做法。第二種,它們可以對欄位重新排序,以便使整數處於4位的邊界上。因為這樣會造成其它有趣的問題,因此,這種方式較少使用。第三種選擇是,允許資料中的整數不處於4位的邊界上,但是把**複製到乙個合適的地方從而使那些整數處於4位的邊界上。這種方式需要一些額外的時間花費,但是,如果必須壓縮的話,那麼它就很有用了。

不同的處理器用不同的方式儲存整數。intel 處理器一般用低位優先方式來儲存整數,換句話說,低位首先被讀寫。大多數其它處理器用高位優先方式來儲存整數。因此,當二進位制檔案在不同平台上讀寫時,你就有可能不得不對位元組重新排序以便得到正確的順序。

在 unix 平台上,還有一種特殊的問題,因為unix可以在sun sparc處理器、hp處理器、ibm power pc、inter的晶元等多種處理器上執行。當從一種處理器轉移到另一種處理器上時,就意味著那些變數的位元組排列順序必須翻轉,以便於它們能滿足新處理器所要求的順序。

用 c# 處理二進位制檔案

用 c# 處理二進位制檔案的話,就會有另外兩項新的挑戰。第一項挑戰是:所有的 .net 語言都是強型別的。因此,你不得不從檔案中的位元組流轉換為你所想要的資料型別。第二項挑戰就是:一些資料型別比它們表面上要複雜的多,需要某種轉換。

型別破壞(type breaking)

因為 .net 語言,包括 c#,都是強型別的,你不能只是任意的從檔案中讀取一段位元組,然後塞到資料結構中就一切ok了。因此當你要破壞型別轉換規則時,你就不得不這樣做了,首先讀取你所需要的位元組數到乙個位元組陣列中,然後把它們從頭到尾的複製到資料結構中。

在 usenet (注:世界性的新聞組網路系統)的文件中搜尋,你會找到幾個構架在 microsoft.public.dotnet層次上的一組程式,它們可以容許你把任何物件轉換為一系列位元組,並可以重新轉換回物件。它們可以在下面位址找到 listing a

複雜的資料型別

在 c++ 中,你明白什麼是物件,什麼是陣列,什麼既不是物件又不是陣列。但是在 c# 中,事情並不像看起來的那樣簡單。乙個字串(string)就是乙個物件,因此也是乙個陣列。因為在 c# 中,既沒有真正的陣列,許多物件也沒有固定尺寸,因此一些複雜資料型別並不適合成為固定尺寸的二進位制資料。

幸好, .net 提供了一種方式來解決這種問題。你可以告訴 c# ,你想怎樣處理你的字串(string)和其它型別的陣列。這將通過 marshalas 屬性來完成。下面這個例子,就是在 c# 中使用字串,這屬性必須要在所控制的資料使用之前被使用:

[marshalas(unmanagedtype.byvaltstr, sizeconst = 50)]

你想要從二進位制檔案中讀取,或者儲存到二進位制檔案中的字串(string)的長度就決定了引數 sizeconst 的大小。這樣就確定了字串長度的最大值。

解決以前的問題

現在,你知道了 .net 引入的問題是怎樣被解決的了。那麼,在後面,你就可以了解到,解決前面所遇到的二進位制檔案問題是那麼的容易。

包裝(pack)

不用麻煩的去設定編譯器來控制如何排列資料。你只需使用 structlayout 屬性就可以使資料依照你的意願來排列或打包。當你需要不同的資料有著不同的包裝方式的時候,這就顯得十分有用了。這就像裝扮你的汽車一樣,任你的喜好。使用 structlayout 屬性就像你很小心的決定是否把每乙個資料都緊湊包裝或者還是只將它們隨便打發,只要它們能夠被重新讀出來就行了。 structlayout 屬性的使用如下面所示:

[structlayout(layoutkind.sequential, pack = 1)]

這樣做可以使資料忽略邊界對齊,讓資料盡可能的緊湊包裝。這個屬性應當和你從二進位制檔案中讀取的任何資料的屬性都保持一致(即:你寫到檔案中的屬性應和從檔案讀出來屬性保持不變)。

你也許會發現,即使給你的資料加上了這個屬性後,也沒有完全解決問題。在某些情況下,你可能不得不進行沉悶冗長的反覆實驗。由於不同計算機和編譯器在二進位制層次上的有著不同的執行處理方式,這就是引起上述問題的原因。特別是在跨平台時,我們都必須特別小心的處理二進位制資料。 .net 是個好工具,適合其它二進位制檔案,但是也並不是乙個完美的工具。

位元組排列順序的翻轉(endian flipping)

讀寫二進位制檔案的經典問題之一就是:某些計算機首先是儲存最不重要的位元組(如:inter),而另外一些計算機是首先儲存最重要的位元組。在 c 和 c++ 中,你不得不手動處理這個問題,而且只能是乙個字段乙個欄位的翻轉。而 .net 框架的優點之一就是:**可以在執行時訪問型別的元資料(metadata),你也就能夠讀取資訊,並使用它來自動解決資料中每一段的位元組排列順序問題。在 listing b 上可以找到源**,你可以了解是如何處理的。

一旦你得知物件的型別,你能夠獲得資料裡的每個部分,並開始檢查每乙個部分,並確定其是否是乙個16位或32位的無符號整數。在任何一種上述情況下,你都可以改變位元組的排序順序,而且不會破壞資料。

注意:你不是用字串類(string)來完成所有的事。是採用高位優先還是低位優先,並不會影響到字串類。那些欄位是不受翻轉**的影響。你也只是要注意無符號整數而已。因為,負數在不同的系統上,並不是使用同一種表示方式的。負數可以只用乙個記號(一位位元組)表示,但是更常用的,卻是使用兩個記號(兩位位元組)表示。這使得負數在跨平台時有些更困難。幸運的是,負數在二進位制檔案中極少使用。

這只是多說幾句了,同樣的,浮點數有時並不是用標準方式表示的。儘管大多數系統是以ieee格式為基礎來設定浮點數的,但是還是有一小部分老的系統使用了其它的格式來設定浮點數的。

用C 讀取二進位制檔案

當想到所有檔案都轉換為 xml時,確實是一件好事。但是,這並非事實。仍舊還有大量的檔案格式不是xml,甚至也不是ascii。二進位制檔案仍然在網路中傳播,儲存在磁碟上,在應用程式之間傳遞。相比之下,在處理這些問題方面,它們比文字檔案顯得更有效率些。在 c 和 c 中,讀取二進位制檔案還是很容易的。除...

C 寫二進位制檔案用matlab讀取

include using namespace std void main file1.write char temp sizeof temp file1.close xx.data檔案,儲存格式為 所有資料都是short型別 前三個分別表示volume在x,y,z方向的解析度linenum,lin...

二進位制讀取檔案內容 C

filestream tempstream new filestream filename,filemode.open binaryreader tempreader new binaryreader tempstream,system.text.encoding.default char cc t...