1. 程式人生 > 實用技巧 >二進位制檔案 .VS. 文字檔案 > python

二進位制檔案 .VS. 文字檔案 > python

【前言】
最近用python讀二進位制檔案,遇到一個問題:我的二進位制檔案裡面摻雜著正常的文字,我想將裡面的文字給剔除掉。解決這個問題就是寫這篇文章的初衷。


一、預備知識

二進位制檔案和文字檔案有啥區別呢?百度知道 裡面有位大佬說:在定義和存取方式上二進位制檔案與文字檔案存在區別。

1、定義上的區別

  • 文字檔案:文字檔案是一種計算機檔案,它是一種典型的順序檔案,其檔案的邏輯結構又屬於流式檔案。簡單的說,文字檔案是基於字元編碼的檔案,常見的編碼有ASCII編碼,UNICODE編碼等等。

  • 二進位制檔案:是基於值編碼的檔案,你可以根據具體應用,指定某個值是什麼意思(這樣一個過程,可以看作是自定義編碼)。使用者一般不能直接讀懂它們,只有通過相應的軟體才能將其顯示出來。二進位制檔案一般是可執行程式、圖形、影象、聲音等等。

  • 從上面可以看出文字檔案與二進位制檔案的區別並不是物理上的,而是邏輯上的。這兩者只是在編碼層次上有差異,文字檔案基本上是定長編碼的(也有非定長的編碼如UTF-8)。而二進位制檔案則可看成是變長編碼,因為是值編碼,多少個位元代表一個值,完全由你決定。

2、儲存方式上的區別

  • 文字工具開啟一個檔案,首先讀取檔案物理上所對應的二進位制位元流,然後按照所選擇的解碼方式來解釋這個流,然後將解釋結果顯示出來。
  • 一般來說,你選取的解碼方式會是ASCII碼形式(ASCII碼的一個字元是8個位元),接下來,它8個位元8個位元地來解釋這個檔案流。
  • 記事本無論開啟什麼檔案都按既定的字元編碼工作(如ASCII碼),所以當他開啟二進位制檔案時,出現亂碼也是很必然的一件事情了,解碼和譯碼不對應。
  • 文字檔案的儲存與其讀取基本上是個逆過程。而二進位制檔案的存取與文字檔案的存取差不多,只是編/解碼方式不同而已。
  • 二進位制檔案就是把記憶體中的資料按其在記憶體中儲存的形式原樣輸出到磁碟中存放,即存放的是資料的原形式。文字檔案是把資料的終端形式的二進位制資料輸出到磁碟上存放,即存放的是資料的終端形式

3、文字檔案和二進位制檔案的優缺點:

  • 一般認為,文字檔案編碼基於字元定長,譯碼容易些;二進位制檔案編碼是變長的,所以它靈活,儲存利用率要高些,譯碼難一些(不同的二進位制檔案格式,有不同的譯碼方式)。
  • 關於空間利用率,想想看,二進位制檔案甚至可以用一個位元來代表一個意思(位操作),而文字檔案任何一個意思至少是一個字元。
  • 在windows下,文字檔案不一定是以ASCII來存貯的,因為ASCII碼只能表示128的標識,你開啟一個txt文件,然後另存為,有個選項是編碼,可以選擇存貯格式,一般來說UTF-8編碼格式相容性要好一些。而二進位制用的計算機原始語言,不存在相容性。
  • 如果儲存的是字元資料,無論採用文字檔案還是二進位制檔案都是沒有任何區別的。如果儲存的是非字元資料,又要看我們使用的情況來決定:
    • 1、如果是需要頻繁的儲存和訪問資料,那麼應該採取二進位制檔案進行存放,這樣可以節省儲存空間和轉換時間。
    • 2、如果需要頻繁的向終端顯示資料或從終端讀入資料,那麼應該採用文字檔案進行存放,這樣可以節省轉換時間。

二、Python 讀取

好像對於文字檔案,不論是二進位制方式讀取還是文字方式讀取,都可以;但是對於二進位制檔案,只可以二進位制方式讀取,用文字方式讀取的話會報錯的。

Python 讀取文字檔案不用多說,關鍵是二進位制檔案。

#讀取文字檔案
f=open('somefile.txt','r');
flist=f.readlines()
f.close()

讀二進位制檔案,就來個壓縮包吧,最近就是搞壓縮包遇到問題,才想寫這篇文章的。

filename=r'C:\Users\OHanlon\Documents\python\temp\ABPO00MDG_R_20191010000_01D_30S_MO.crx.gz'
fblist=fb.readlines()
l1=fblist[0]    #b'HTTP/1.1 200 OK\r\n'
l15=fblist[14]  #b"\xe3\xe0\x87\x91\xc..
#上面只是一部分
type(l1)         #<class 'bytes'>
type(l15)        #<class 'bytes'>

上面這個檔案情況是這樣的:用npp開啟的時候,可以看到它前面有幾行是文字,然後後面是亂碼。通過上面python以二進位制方式讀取的話,直觀上看,可以很明顯地區分出來文字和二進位制語句。


Python2 和Python3 讀取二進位制檔案的區別

  • python3:
  • python2:

從上面可以看出來:python2 以二進位制方式讀檔案讀出來的是字串,python3讀二進位制檔案,讀出來的是bytes。

我上面是用python3讀取的,下面先考慮python3的情況。

sl1=str(l1)    # "b'HTTP/1.1 200 OK\\r\\n'"
sl15=str(l15)  # 'b"\\xe3\\xe0\\x87\\x91\\xc..

將bytes 轉化成 str ;這樣就很容易看出來區別了:

好,上面已經解決了python3 的問題:r'\x' in str(xx) 返回 true 就是二進位制語句,返回false就是正常的文字語句。那麼對於python2 怎麼解決呢?(實際上我的問題已經得以解決,但是處於好奇,多嘗試一下嘛)


先切換到 16 進位制檢視看下檔案:

注:這個檔案,前13行是文字。
發現並沒有好的標誌,做了初步嘗試:

然後在網上搜了搜,發現有篇文章:

https://blog.csdn.net/houyj1986/article/details/20879071

文章中的小函式如下(注意,是Python2):

#Check if a string is text or binary
'''If the string contains control or more than 30% of the characters in the string are 1, then it is binary data'''
import string

text = ''.join(map(chr,range(32,127))) + '\r\n\t\b'
_null_trans = string.maketrans('','')

def istext(s,text = text,threshold = 0.30):
	if '\0' in s:
		return False
	if not s:
		return True
	t = s.translate(_null_trans,text) #delete char in text for s
	return len(t)/(len(s) * 1.0) <= threshold

進行測試:

file='ABPO00MDG_R_20191010000_01D_30S_MO.crx.gz'
fb=open(file,'rb')
flist=fb.readlines()
l1=flist[0]
l15=flist[14]
fb.close()
import test; print test.istext(l1); print test.istext(l15)


perfect!

至此,在python2 和python3 中都已完美解決我遇到的問題!

三、Python 中 str 和 bytes 的相互轉換

>>> str='ohanlon'
>>> bstr=str.encode('utf-8')
>>> bstr
b'ohanlon'
>>> str1=bstr.decode('utf-8')
>>> str1
'ohanlon'
>>> del str  #得把str刪了,不然下面那行會將str認為是變數str,這樣就執行不了。
>>> str(bstr)
"b'ohanlon'"
>>> repr(bstr)
"b'ohanlon'"

【文章總結】

  • bstr=str.encode('utf-8') str轉bytes
  • str1=bstr.decode('utf-8') bytes轉str
  • python3與python2的區別:python2支援print xxprint(xx),而python3 只支援後者。python3 以二進位制形式讀檔案出來的是bytes,而python2以二進位制形式讀檔案讀出來的是str。
  • 為解決消除二進位制檔案中的文字資訊,在python3中的解決方案是r'\x' in str(xx) 返回 true 就是二進位制語句,返回false就是正常的文字語句;在python2 中的解決方案是加個小函式,istext(xx) 返回 false 就是二進位制語句,返回true就是正常的文字語句。
    python2中的小函式如下:
#Check if a string is text or binary
'''If the string contains control or more than 30% of the characters in the string are 1, then it is binary data'''
import string

text = ''.join(map(chr,range(32,127))) + '\r\n\t\b'
_null_trans = string.maketrans('','')

def istext(s,text = text,threshold = 0.30):
	if '\0' in s:
		return False
	if not s:
		return True
	t = s.translate(_null_trans,text) #delete char in text for s
	return len(t)/(len(s) * 1.0) <= threshold