Python 二進制文件讀寫與解析詳解
一、二進制文件基礎
1. 為什麼需要二進制文件?
- 效率:二進制文件比文本文件更緊湊,讀寫更快
- 結構:存儲結構化數據(如圖像、音頻、數據庫文件)
- 精度:直接存儲原始二進制數據,無編碼轉換
2. 基本操作模式
# 基本打開模式
with open('file.bin', 'rb') as f: # 讀取二進制
pass
with open('file.bin', 'wb') as f: # 寫入二進制
pass
with open('file.bin', 'ab') as f: # 追加二進制
pass
with open('file.bin', 'rb+') as f: # 讀寫二進制
pass
二、低級二進制讀寫(struct模塊)
1. struct模塊基礎
import struct
# 打包數據
packed_data = struct.pack('i', 42) # 4字節整數
print(packed_data) # b'*\x00\x00\x00'
# 解包數據
value = struct.unpack('i', packed_data)[0]
print(value) # 42
2. 格式字符串語法
格式字符:
c: char (1字節)
b: signed char (1字節)
B: unsigned char (1字節)
h: short (2字節)
H: unsigned short (2字節)
i: int (4字節)
I: unsigned int (4字節)
l: long (4字節)
L: unsigned long (4字節)
q: long long (8字節)
Q: unsigned long long (8字節)
f: float (4字節)
d: double (8字節)
s: char[] (字符串)
p: char[] (pascal字符串)
?: bool (1字節)
3. 複雜結構打包
# 打包多個數據
data = struct.pack('i10sf', 123, b'hello', 3.14)
# 解包
id_num, name, value = struct.unpack('i10sf', data)
name = name.rstrip(b'\x00').decode() # 清理空字節並解碼
# 使用calcsize計算大小
size = struct.calcsize('i10sf') # 4 + 10 + 4 = 18字節
三、高級二進制讀寫
1. 使用bytes和bytearray
# 創建字節數據
data = bytes([0x48, 0x65, 0x6C, 0x6C, 0x6F]) # "Hello"
# 讀取二進制文件
with open('data.bin', 'rb') as f:
# 讀取指定字節數
chunk = f.read(1024)
# 讀取整個文件
data = f.read()
# 讀取一行(直到換行符)
line = f.readline()
# 按字節讀取
byte = f.read(1)
while byte:
# 處理字節
byte = f.read(1)
# 寫入二進制文件
with open('output.bin', 'wb') as f:
f.write(b'\x00\x01\x02\x03')
f.write(bytes([255, 128, 64]))
2. 內存視圖(memoryview)
# 高效處理大文件
with open('large.bin', 'rb') as f:
# 內存映射文件
mv = memoryview(f.read())
# 切片操作不復制數據
header = mv[0:100]
data_chunk = mv[100:1100]
四、實際應用示例
1. 解析BMP文件頭
def parse_bmp_header(filename):
with open(filename, 'rb') as f:
# 讀取文件頭(14字節)
file_header = f.read(14)
# 解析BMP文件頭
signature, file_size, reserved, data_offset = \
struct.unpack('<2sIHHI', file_header)
print(f"文件簽名: {signature}")
print(f"文件大小: {file_size} 字節")
print(f"數據偏移: {data_offset} 字節")
# 讀取DIB頭(至少40字節)
dib_header = f.read(40)
# 解析DIB頭
header_size, width, height, planes, bits_per_pixel, \
compression, image_size, x_ppm, y_ppm, colors_used, \
important_colors = struct.unpack('<IiiHHIIiiII', dib_header)
print(f"圖像尺寸: {width} x {height}")
print(f"位深度: {bits_per_pixel}")
print(f"壓縮方式: {compression}")
2. 讀取PNG文件結構
def read_png_chunks(filename):
PNG_SIGNATURE = b'\x89PNG\r\n\x1a\n'
with open(filename, 'rb') as f:
signature = f.read(8)
if signature != PNG_SIGNATURE:
raise ValueError("不是有效的PNG文件")
while True:
# 讀取塊長度(4字節)
chunk_length_data = f.read(4)
if not chunk_length_data:
break
chunk_length = struct.unpack('>I', chunk_length_data)[0]
# 讀取塊類型(4字節)
chunk_type = f.read(4).decode('ascii')
# 讀取塊數據
chunk_data = f.read(chunk_length)
# 讀取CRC(4字節)
crc = f.read(4)
print(f"塊類型: {chunk_type}, 長度: {chunk_length}")
if chunk_type == 'IEND':
break
3. 自定義二進制數據格式
class BinaryDataFormat:
def __init__(self):
self.data = []
def add_record(self, id, name, value):
"""添加記錄"""
name_bytes = name.encode('utf-8')
# 格式:ID(4B) + 名字長度(2B) + 名字 + 值(4B)
record = struct.pack('IH', id, len(name_bytes))
record += name_bytes
record += struct.pack('f', value)
self.data.append(record)
def save(self, filename):
"""保存到文件"""
with open(filename, 'wb') as f:
# 寫入記錄數量
f.write(struct.pack('I', len(self.data)))
# 寫入所有記錄
for record in self.data:
f.write(record)
@staticmethod
def load(filename):
"""從文件加載"""
with open(filename, 'rb') as f:
# 讀取記錄數量
num_records = struct.unpack('I', f.read(4))[0]
records = []
for _ in range(num_records):
# 讀取記錄頭
id, name_len = struct.unpack('IH', f.read(6))
# 讀取名字
name = f.read(name_len).decode('utf-8')
# 讀取值
value = struct.unpack('f', f.read(4))[0]
records.append({
'id': id,
'name': name,
'value': value
})
return records
4. 處理網絡數據包
def parse_ethernet_frame(data):
"""解析以太網幀"""
if len(data) < 14:
raise ValueError("數據太短")
# 解析以太網頭部
dest_mac = ':'.join(f'{b:02x}' for b in data[0:6])
src_mac = ':'.join(f'{b:02x}' for b in data[6:12])
eth_type = struct.unpack('>H', data[12:14])[0]
print(f"目標MAC: {dest_mac}")
print(f"源MAC: {src_mac}")
print(f"以太網類型: 0x{eth_type:04x}")
# 繼續解析IP頭部等...
五、性能優化技巧
1. 批量讀取與解析
def batch_read_binary(filename, chunk_size=4096, record_size=16):
"""批量讀取和解析二進制記錄"""
records = []
with open(filename, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
# 按記錄大小分割
for i in range(0, len(chunk), record_size):
record_data = chunk[i:i+record_size]
if len(record_data) == record_size:
# 解析記錄
record = struct.unpack('IIf', record_data)
records.append(record)
return records
2. 使用array模塊
import array
# 高效處理數值數組
arr = array.array('f') # 浮點數數組
arr.fromfile(open('data.bin', 'rb'), 1000) # 讀取1000個浮點數
# 寫入數組
arr.tofile(open('output.bin', 'wb'))
3. 使用numpy處理科學數據
import numpy as np
# 從二進制文件加載數組
data = np.fromfile('data.bin', dtype=np.float32)
# 保存數組到二進制文件
array_data = np.array([1.0, 2.0, 3.0], dtype=np.float32)
array_data.tofile('output.bin')
六、調試與錯誤處理
1. 驗證二進制數據
def validate_binary_data(data, expected_size=None, checksum=None):
"""驗證二進制數據的完整性"""
if expected_size and len(data) != expected_size:
raise ValueError(f"數據大小錯誤: 期望 {expected_size}, 實際 {len(data)}")
if checksum:
# 計算簡單的校驗和
calc_checksum = sum(data) & 0xFF
if calc_checksum != checksum:
raise ValueError(f"校驗和錯誤: 期望 {checksum}, 實際 {calc_checksum}")
return True
2. 調試輸出
def hex_dump(data, offset=0, length=None, width=16):
"""生成十六進制轉儲"""
if length is None:
length = len(data)
result = []
for i in range(0, min(length, len(data)), width):
# 偏移地址
line = f"{offset + i:08x}: "
# 十六進制
hex_str = ' '.join(f"{b:02x}" for b in data[i:i+width])
hex_str = hex_str.ljust(width * 3 - 1)
# ASCII
ascii_str = ''.join(chr(b) if 32 <= b < 127 else '.'
for b in data[i:i+width])
result.append(f"{line}{hex_str} {ascii_str}")
return '\n'.join(result)
七、總結與最佳實踐
1. 最佳實踐
- 明確字節序:使用
<(小端)或>(大端)前綴指定字節序 - 錯誤處理:始終驗證讀取的數據長度
- 資源管理:使用
with語句確保文件正確關閉 - 性能考慮:對大文件使用緩衝和內存映射
2. 常用工具
- hexdump模塊:專業的十六進制轉儲
- construct庫:聲明式二進制數據解析
- bitstring模塊:位級數據操作
- pylibelf:ELF文件解析
- pefile:Windows PE文件解析
3. 完整示例
def binary_file_processor(input_file, output_file):
"""完整的二進制文件處理示例"""
try:
with open(input_file, 'rb') as infile, \
open(output_file, 'wb') as outfile:
# 讀取並驗證文件頭
header = infile.read(4)
if header != b'DATA':
raise ValueError("無效的文件格式")
outfile.write(header)
# 處理數據塊
while True:
# 讀取塊頭
chunk_header = infile.read(8)
if not chunk_header:
break
chunk_type, chunk_size = struct.unpack('<II', chunk_header)
# 讀取塊數據
chunk_data = infile.read(chunk_size)
if len(chunk_data) != chunk_size:
raise ValueError("文件損壞或不完整")
# 處理並寫入數據
processed_data = process_chunk(chunk_type, chunk_data)
outfile.write(struct.pack('<II', chunk_type, len(processed_data)))
outfile.write(processed_data)
print("文件處理完成")
except IOError as e:
print(f"文件操作錯誤: {e}")
except struct.error as e:
print(f"數據解析錯誤: {e}")
except ValueError as e:
print(f"數據驗證錯誤: {e}")
通過掌握這些技術,你可以有效地處理各種二進制文件格式,從簡單的數據存儲到複雜的文件格式解析都能遊刃有餘。
結束語 Flutter是一個由Google開發的開源UI工具包,它可以讓您在不同平台上創建高質量、美觀的應用程序,而無需編寫大量平台特定的代碼。我將學習和深入研究Flutter的方方面面。從基礎知識到高級技巧,從UI設計到性能優化,歡飲關注一起討論學習,共同進入Flutter的精彩世界!