前言
最近在用Java做一個文件格式轉(zhuǎn)化的工具(github地址:https://github.com/lhing17/waterConverter.git)谊迄,希望通過簡單的調(diào)用實現(xiàn)一些常用格式間的互相轉(zhuǎn)換,實質(zhì)上是一些處理不同文件格式的工具包的整合氓辣。借此機會诵次,也對Java怎么處理不同格式的文件有了更深入的了解账蓉,希望寫一系列的文章作為記錄。作為開篇逾一,先來聊一聊什么是二進制文件铸本,以及二進制文件到底是怎么存儲信息的。
文本文件 vs 二進制文件
我們常說遵堵,文件分為文本文件和二進制文件箱玷,這其實是一種很糙的分類方式。文件是文本文件還是二進制文件陌宿,這是邏輯上的概念锡足,而不是物理上的概念。在物理上壳坪,文件都是基于二進制來存儲的舶得,文件存儲的基本單位是字節(jié),每個字節(jié)由8個二進制位組成爽蝴。如果整個文件能夠被某種軟件全部解碼為字符串沐批,我們就認為這個文件是文本文件纫骑。也就是說,文本文件是一種特殊的二進制文件珠插。
同樣道理惧磺,某種二進制文件如果可以按照位圖的方式去解碼颖对,它就可以顯示為一張圖片捻撑;如果可以按照
MPEG-3的規(guī)則解碼,它就可以播放為音頻缤底。一些常見的文件格式顾患,都是使用其相應(yīng)的規(guī)范進行編碼的。因此个唧,我們可以根據(jù)規(guī)范編寫相應(yīng)解碼的軟件江解,用于讀取這些格式的文件。也有一些二進制文件的編碼格式是程序開發(fā)者自定義的徙歼,這樣的文件對于普通的用戶來說就幾乎是保密的犁河。
規(guī)范的二進制文件通常都是由多個多字節(jié)的序列組成的,每個多字節(jié)的序列承載一部分信息魄梯。我們以Java的Class文件為例桨螺,Class文件是以無符號數(shù)和表兩種結(jié)構(gòu)來存儲信息。
無符號數(shù)屬于基本數(shù)據(jù)類型酿秸,以u1灭翔、u2、u4辣苏、u8來分別代表1個字節(jié)肝箱、2個字節(jié)、4個字節(jié)和8個字節(jié)的無符號數(shù)稀蟋,無符號數(shù)可以用來描述數(shù)字煌张、索引引用、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值退客。
表是由多個無符號數(shù)或者其他表作為數(shù)據(jù)項構(gòu)成的復(fù)合數(shù)據(jù)類型唱矛,為了便于區(qū)分,所有表的命名都習(xí)慣性地以“_info”結(jié)尾井辜。表用于描述有層次關(guān)系的復(fù)合數(shù)據(jù)绎谦,整個Class文件本質(zhì)上也可以視作是一張表,這張表由下表所示的數(shù)據(jù)項按嚴(yán)格順序排列構(gòu)成粥脚。
表1 Class文件格式
類型 | 名稱 | 數(shù)量 |
---|---|---|
u4 | magic | 1 |
u2 | minor_version | 1 |
u2 | major_version | 1 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count-1 |
u2 | access_flags | 1 |
u2 | this_class | 1 |
u2 | super_class | 1 |
u2 | interfaces_count | 1 |
u2 | interfaces | interfaces_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | methods_count | 1 |
method_info | methods | methods_count |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
使用Hexdump類工具查看二進制文件
有的時候窃肠,為了開發(fā)方便,我們需要去查看二進制文件的結(jié)構(gòu)刷允。前面提到冤留,二進制文件的基本單位是字節(jié)碧囊,一個字節(jié)是由8個二進制位(0或1)組成的。二進制書寫和閱讀起來都很不方便纤怒,為了讀寫方便糯而,我們通常把二進制轉(zhuǎn)化為16進制。16是2的4次方泊窘,因此一個16進制數(shù)可以代表4個2進制位熄驼。這樣,我們就可以用兩個16進制數(shù)來代表一個字節(jié)烘豹。有一些工具可以用來查看文件的16進制表示瓜贾,如Hexdump。Hexdump最初是linux系統(tǒng)上的命令携悯,現(xiàn)在VSCode等工具都提供了hexdump的插件祭芦。
圖1 VSCode hexdump插件查看文件效果圖
如上圖,左側(cè)是二進制文件的16進制表示憔鬼,右側(cè)是嘗試解碼為字符的表示龟劲。
little-endian vs big-endian
當(dāng)我們使用多個字節(jié)的序列來承載信息時,就出現(xiàn)了字節(jié)存儲順序的問題轴或。我們可以把高位字節(jié)放在前面昌跌,也可以把低位字節(jié)放在前面,這就是我們平時說的little-endian和big-endian侮叮。
1)Little-endian:將低序字節(jié)存儲在起始地址(低位編址)
2)Big-endian:將高序字節(jié)存儲在起始地址(高位編址)
舉個例子:
我們有一個整型數(shù)據(jù)1避矢,它在內(nèi)存中占據(jù)了四個字節(jié)的空間,它的16進制表示為:
0x00 00 00 01
在內(nèi)存中怎么存儲呢?
如果你的CPU是intel x86架構(gòu)的(基本上就是通常我們說的奔騰cpu),那么就是0x01 0x00 0x00 0x00 , 這也就是所謂的little-endian, 低字節(jié)存放在內(nèi)存的低位囊榜。如果你的CPU是老式AMD系列的(很老很老的那種审胸,因為最新的AMD系列已經(jīng)是x86架構(gòu)了), 它的字節(jié)序就是big-endian, 其內(nèi)存存儲就是 0x00 0x00 0x00 0x01在內(nèi)存中從高字節(jié)開始存放。
Java是big-endian還是little-endian?
Java默認采用big-endian方式卸勺,如各種讀寫流中的readInt()方法都是從高位到低位讀取四個字節(jié)轉(zhuǎn)化為整數(shù)砂沛。如果文件的讀寫都是通過Java來實現(xiàn)的,我們通常不需要去關(guān)注字節(jié)序的問題曙求。但是如果與那些不是使用Java編寫的程序交換數(shù)據(jù)文件時碍庵,就需要考慮字節(jié)序的問題了。
參考文獻
周志明《深入理解Java虛擬機》
Big Endian 和 Little Endian 詳解悟狱。原文鏈接:https://blog.csdn.net/waitingbb123/java/article/details/80504093