Python 二进制文件
在Python编程中,文件处理是一项基础而重要的技能。除了常见的文本文件外,二进制文件的处理也是实际应用中不可或缺的部分。二进制文件包含非文本数据,如图像、音频、视频或其他特定格式的数据,理解如何操作这些文件对于开发各类应用程序至关重要。
什么是二进制文件?
二进制文件与文本文件不同,它们存储的是原始的字节序列,而不是人类可读的字符。这些文件通常包含特定结构的数据,需要使用专门的程序或库来解释。
备注
文本文件与二进制文件的主要区别:
- 文本文件存储的是字符,通常使用UTF-8等编码方式
- 二进制文件存储的是原始字节,没有特定的字符编码
Python 中的二进制数据类型
在处理二进制文件之前,我们需要先了解Python中用于处理二进制数据的主要类型:
bytes对象
bytes
是Python中不可变的字节序列,类似于字符串但专门用于表示字节数据。
python
# 创建bytes对象的几种方式
b1 = b'Hello' # 字节字面量
b2 = bytes([72, 101, 108, 108, 111]) # 从整数列表创建
b3 = bytes('Hello', 'utf-8') # 从字符串编码创建
print(b1) # 输出: b'Hello'
print(list(b1)) # 输出: [72, 101, 108, 108, 111]
输出:
b'Hello'
[72, 101, 108, 108, 111]
bytearray对象
bytearray
是bytes
的可变版本,允许修改其内容。
python
# 创建和修改bytearray
ba = bytearray(b'Hello')
print(ba) # 输出: bytearray(b'Hello')
ba[0] = 74 # 修改第一个字节
print(ba) # 输出: bytearray(b'Jello')
输出:
bytearray(b'Hello')
bytearray(b'Jello')
二进制文件的读写操作
打开二进制文件
在Python中,通过在open()
函数的模式参数中添加'b'
来指定二进制模式:
python
# 打开二进制文件进行读取
with open('example.bin', 'rb') as file:
data = file.read()
# 打开二进制文件进行写入
with open('output.bin', 'wb') as file:
file.write(b'Binary data here')
读取二进制文件
python
# 读取整个二进制文件
with open('example.bin', 'rb') as file:
data = file.read()
print(type(data)) # 输出: <class 'bytes'>
print(len(data)) # 打印文件字节数
# 按块读取大型二进制文件
with open('large_file.bin', 'rb') as file:
chunk = file.read(1024) # 每次读取1KB
while chunk:
# 处理数据块
chunk = file.read(1024)
写入二进制文件
python
# 写入二进制数据
binary_data = bytes([0, 1, 2, 3, 4, 5])
with open('data.bin', 'wb') as file:
file.write(binary_data)
# 写入多个数据块
with open('multiple_chunks.bin', 'wb') as file:
file.write(b'First chunk')
file.write(b'Second chunk')
二进制文件操作实例
复制图像文件
下面是一个复制二进制图像文件的简单例子:
python
def copy_image(source, destination):
with open(source, 'rb') as src:
with open(destination, 'wb') as dst:
# 每次读取4KB数据
buffer_size = 4096
buffer = src.read(buffer_size)
while buffer:
dst.write(buffer)
buffer = src.read(buffer_size)
print(f"已成功将 {source} 复制到 {destination}")
# 使用函数复制图片
copy_image('original.jpg', 'copy.jpg')
创建简单的二进制文件格式
以下是如何创建一个简单的自定义二进制文件格式,存储一些整数和字符串:
python
import struct
def write_custom_binary(filename, numbers, text):
"""
写入自定义二进制格式文件
文件结构:
- 4字节: 整数数量
- 每个整数占4字节
- 4字节: 字符串长度
- N字节: UTF-8编码的字符串
"""
with open(filename, 'wb') as file:
# 写入整数数量
file.write(struct.pack('i', len(numbers)))
# 写入所有整数
for num in numbers:
file.write(struct.pack('i', num))
# 编码并写入字符串
encoded_text = text.encode('utf-8')
file.write(struct.pack('i', len(encoded_text)))
file.write(encoded_text)
def read_custom_binary(filename):
"""从自定义二进制文件中读取数据"""
with open(filename, 'rb') as file:
# 读取整数数量
count = struct.unpack('i', file.read(4))[0]
# 读取所有整数
numbers = []
for _ in range(count):
num = struct.unpack('i', file.read(4))[0]
numbers.append(num)
# 读取字符串
text_length = struct.unpack('i', file.read(4))[0]
text = file.read(text_length).decode('utf-8')
return numbers, text
# 写入示例数据
write_custom_binary('data.bin', [10, 20, 30, 40], "Hello Binary World!")
# 读取数据并验证
nums, txt = read_custom_binary('data.bin')
print(f"读取的数字: {nums}")
print(f"读取的文本: {txt}")
输出:
读取的数字: [10, 20, 30, 40]
读取的文本: Hello Binary World!
使用struct模块处理二进制数据
Python的struct
模块提供了将Python值与C结构体表示的字节串互相转换的功能,这在处理二进制文件时非常有用。
python
import struct
# 将整数打包为4字节的二进制数据
packed_int = struct.pack('i', 42)
print(packed_int) # 输出: b'*\x00\x00\x00' (具体输出取决于系统的字节顺序)
# 解包二进制数据为整数
unpacked_int = struct.unpack('i', packed_int)[0]
print(unpacked_int) # 输出: 42
# 打包多个值
# 'i'表示4字节整数,'f'表示4字节浮点数,'5s'表示5个字符的字节串
packed_data = struct.pack('if5s', 10, 3.14, b'hello')
print(packed_data)
# 解包多个值
unpacked_data = struct.unpack('if5s', packed_data)
print(unpacked_data) # 输出: (10, 3.140000104904175, b'hello')
struct格式字符
以下是一些常用的格式字符:
格式 | C类型 | Python类型 | 大小(字节) |
---|---|---|---|
c | char | 长度为1的bytes | 1 |
b | signed char | 整数 | 1 |
B | unsigned char | 整数 | 1 |
h | short | 整数 | 2 |
H | unsigned short | 整数 | 2 |
i | int | 整数 | 4 |
I | unsigned int | 整数 | 4 |
q | long long | 整数 | 8 |
Q | unsigned long long | 整数 | 8 |
f | float | 浮点数 | 4 |
d | double | 浮点数 | 8 |
s | char[] | bytes | 可变 |
实际应用案例
案例1:解析WAV音频文件头
WAV文件是一种常见的音频文件格式,其文件头包含重要的元数据。以下代码展示如何解析WAV文件头:
python
import struct
import wave
def print_wav_info(wav_file):
"""打印WAV文件的基本信息"""
with wave.open(wav_file, 'rb') as wav:
# 获取基本信息
n_channels = wav.getnchannels()
sample_width = wav.getsampwidth()
frame_rate = wav.getframerate()
n_frames = wav.getnframes()
compression_type = wav.getcomptype()
compression_name = wav.getcompname()
# 计算时长(秒)
duration = n_frames / frame_rate
# 打印信息
print(f"文件: {wav_file}")
print(f"通道数: {n_channels}")
print(f"采样宽度: {sample_width} 字节")
print(f"采样率: {frame_rate} Hz")
print(f"帧数: {n_frames}")
print(f"压缩类型: {compression_type}")
print(f"压缩名称: {compression_name}")
print(f"时长: {duration:.2f} 秒")
# 使用示例
try:
print_wav_info('example.wav') # 需要有一个WAV文件用于测试
except FileNotFoundError:
print("请替换为有效的WAV文件路径")
案例2:创建和读取简单的二进制数据库
下面是一个简单的二进制数据库实现,用于存储学生记录:
python
import struct
import os
class StudentRecord:
def __init__(self, id, name, age, score):
self.id = id
self.name = name
self.age = age
self.score = score
def __str__(self):
return f"ID: {self.id}, 姓名: {self.name}, 年龄: {self.age}, 成绩: {self.score}"
class BinaryStudentDB:
def __init__(self, filename):
self.filename = filename
# 如果文件不存在,创建一个空文件
if not os.path.exists(filename):
with open(filename, 'wb') as f:
# 写入记录数量(初始为0)
f.write(struct.pack('i', 0))
def add_student(self, student):
"""向数据库添加学生记录"""
with open(self.filename, 'r+b') as f:
# 读取当前记录数
f.seek(0)
record_count = struct.unpack('i', f.read(4))[0]
# 移动到文件末尾准备添加新记录
f.seek(0, 2) # 2表示从文件末尾计算偏移量
# 写入学生ID (4字节)
f.write(struct.pack('i', student.id))
# 写入姓名长度和姓名
name_bytes = student.name.encode('utf-8')
f.write(struct.pack('i', len(name_bytes)))
f.write(name_bytes)
# 写入年龄和成绩
f.write(struct.pack('if', student.age, student.score))
# 更新记录数
f.seek(0)
f.write(struct.pack('i', record_count + 1))
def get_all_students(self):
"""获取所有学生记录"""
students = []
with open(self.filename, 'rb') as f:
# 读取记录数
record_count = struct.unpack('i', f.read(4))[0]
for _ in range(record_count):
# 读取ID
student_id = struct.unpack('i', f.read(4))[0]
# 读取姓名
name_length = struct.unpack('i', f.read(4))[0]
name = f.read(name_length).decode('utf-8')
# 读取年龄和成绩
age, score = struct.unpack('if', f.read(8))
# 创建学生对象并添加到列表
students.append(StudentRecord(student_id, name, age, score))
return students
# 使用示例
db = BinaryStudentDB('students.bin')
# 添加一些学生记录
db.add_student(StudentRecord(1, "张三", 18, 90.5))
db.add_student(StudentRecord(2, "李四", 19, 85.0))
db.add_student(StudentRecord(3, "王五", 20, 92.5))
# 读取并打印所有学生记录
print("学生记录:")
for student in db.get_all_students():
print(student)
输出:
学生记录:
ID: 1, 姓名: 张三, 年龄: 18, 成绩: 90.5
ID: 2, 姓名: 李四, 年龄: 19, 成绩: 85.0
ID: 3, 姓名: 王五, 年龄: 20, 成绩: 92.5
总结
Python提供了强大而灵活的二进制文件处理能力,主要通过以下几个方面:
- 二进制数据类型:
bytes
和bytearray
用于表示和操作字节序列 - 二进制文件I/O:通过在文件模式中添加
'b'
来实现二进制文件的读写 - 结构化二进制数据:使用
struct
模块可以轻松处理固定格式的二进制数据
掌握这些技能对于处理图像、音频、视频、网络协议实现以及自定义二进制格式的数据存储和传输都至关重要。
提示
在处理二进制数据时,务必要注意数据的字节顺序(大端序或小端序)以及数据的结构和对齐方式,这些因素可能影响到跨平台数据交换的正确性。
练习
- 创建一个程序,读取一个二进制图片文件并将其复制到一个新文件中。
- 实现一个函数,用于反转二进制文件的内容(即第一个字节变为最后一个,以此类推)。
- 设计一个简单的二进制文件格式来存储一系列的温度记录,每个记录包含日期、时间和温度值。
- 使用
struct
模块解析一个BMP图像文件的头部信息,提取图像的宽度、高度和色彩深度。 - 创建一个二进制文件加密工具,使用简单的XOR加密方法。