第83集:二进制文件操作

学习目标

  • 理解二进制文件与文本文件的区别
  • 掌握二进制文件的读写操作
  • 学会使用struct模块处理二进制数据
  • 掌握图片、音频等二进制文件的处理
  • 了解二进制文件的常见应用场景

一、二进制文件概述

1.1 什么是二进制文件

二进制文件是以二进制形式存储数据的文件,与文本文件不同,二进制文件的内容不是人类可读的文本,而是计算机可以直接处理的二进制数据。

1.2 二进制文件与文本文件的区别

特性 文本文件 二进制文件
存储方式 字符编码(如UTF-8) 原始二进制数据
可读性 人类可读 人类不可读
文件大小 较大(编码开销) 较小(紧凑存储)
处理方式 按字符处理 按字节处理
应用场景 配置文件、日志等 图片、音频、视频等

1.3 常见二进制文件类型

  • 图片文件:PNG、JPG、GIF、BMP等
  • 音频文件:MP3、WAV、OGG等
  • 视频文件:MP4、AVI、MKV等
  • 压缩文件:ZIP、RAR、7Z等
  • 可执行文件:EXE、DLL等
  • 数据文件:数据库文件、序列化数据等

二、打开二进制文件

2.1 二进制打开模式

在文本模式的基础上添加'b'字符即可打开二进制模式:

模式 描述
'rb' 二进制只读
'wb' 二进制写入(覆盖)
'ab' 二进制追加
'rb+' 二进制读写
'wb+' 二进制读写(覆盖)
'ab+' 二进制读写(追加)

2.2 打开二进制文件示例

# 读取二进制文件
with open('image.png', 'rb') as f:
    data = f.read()

# 写入二进制文件
with open('output.bin', 'wb') as f:
    f.write(b'\x00\x01\x02\x03')

三、读取二进制文件

3.1 读取整个文件

with open('file.bin', 'rb') as f:
    data = f.read()
    print(type(data))  # <class 'bytes'>

3.2 读取指定字节数

with open('file.bin', 'rb') as f:
    # 读取前100字节
    chunk = f.read(100)
    print(chunk)

3.3 逐字节读取

with open('file.bin', 'rb') as f:
    while True:
        byte = f.read(1)
        if not byte:
            break
        print(byte)

四、写入二进制文件

4.1 写入字节串

# 写入字节串
data = b'Hello, Binary!'
with open('output.bin', 'wb') as f:
    f.write(data)

4.2 写入单个字节

with open('output.bin', 'wb') as f:
    f.write(b'\x00')
    f.write(b'\x01')
    f.write(b'\x02')

4.3 使用bytes()函数

# 从列表创建字节串
data = bytes([0x00, 0x01, 0x02, 0x03])
with open('output.bin', 'wb') as f:
    f.write(data)

4.4 使用bytearray()

# 创建可变的字节数组
data = bytearray([0x00, 0x01, 0x02, 0x03])
data.append(0x04)
with open('output.bin', 'wb') as f:
    f.write(data)

五、struct模块

5.1 struct模块简介

struct模块提供了在Python值和C结构体之间转换的功能,可以方便地处理二进制数据。

5.2 常用格式字符

格式字符 C类型 Python类型 大小(字节)
'b' signed char int 1
'B' unsigned char int 1
'h' short int 2
'H' unsigned short int 2
'i' int int 4
'I' unsigned int int 4
'f' float float 4
'd' double float 8
's' char[] bytes -

5.3 pack()函数

将Python值打包成二进制数据。

import struct

# 打包一个整数和一个浮点数
data = struct.pack('if', 42, 3.14)
print(data)  # b'*\x00\x00\x00\x1f\x85\xebQ'

5.4 unpack()函数

将二进制数据解包成Python值。

import struct

data = b'*\x00\x00\x00\x1f\x85\xebQ'
values = struct.unpack('if', data)
print(values)  # (42, 3.140000104904175)

5.5 calcsize()函数

计算格式字符串对应的大小。

import struct

size = struct.calcsize('if')
print(size)  # 8 (4字节int + 4字节float)

六、处理图片文件

6.1 读取图片文件

# 读取图片文件
with open('image.png', 'rb') as f:
    image_data = f.read()
    print(f"图片大小:{len(image_data)} 字节")

6.2 复制图片文件

# 复制图片文件
def copy_image(src, dst):
    with open(src, 'rb') as src_file:
        with open(dst, 'wb') as dst_file:
            dst_file.write(src_file.read())

copy_image('source.png', 'copy.png')

6.3 读取图片头部信息

# 读取PNG文件头部信息
def read_png_header(filename):
    with open(filename, 'rb') as f:
        header = f.read(8)
        if header[:8] == b'\x89PNG\r\n\x1a\n':
            print("这是一个PNG文件")
            # 读取IHDR chunk
            chunk_length = struct.unpack('>I', f.read(4))[0]
            chunk_type = f.read(4)
            print(f"Chunk类型:{chunk_type}")
        else:
            print("这不是PNG文件")

read_png_header('image.png')

七、处理音频文件

7.1 读取WAV文件头部

import struct

def read_wav_header(filename):
    with open(filename, 'rb') as f:
        # 读取RIFF头
        riff = f.read(4)
        file_size = struct.unpack('<I', f.read(4))[0]
        wave = f.read(4)
        
        # 读取fmt子块
        fmt = f.read(4)
        fmt_size = struct.unpack('<I', f.read(4))[0]
        audio_format = struct.unpack('<H', f.read(2))[0]
        num_channels = struct.unpack('<H', f.read(2))[0]
        sample_rate = struct.unpack('<I', f.read(4))[0]
        byte_rate = struct.unpack('<I', f.read(4))[0]
        block_align = struct.unpack('<H', f.read(2))[0]
        bits_per_sample = struct.unpack('<H', f.read(2))[0]
        
        print(f"音频格式:{audio_format}")
        print(f"声道数:{num_channels}")
        print(f"采样率:{sample_rate} Hz")
        print(f"位深度:{bits_per_sample} bit")

read_wav_header('audio.wav')

八、二进制文件应用场景

8.1 数据序列化

import struct

# 将数据序列化为二进制
def serialize_data(data):
    # 格式:3个整数,2个浮点数,1个字符串(10字节)
    packed = struct.pack('3if10s', 
                        data['a'], data['b'], data['c'],
                        data['x'], data['y'],
                        data['name'].encode('utf-8'))
    return packed

# 反序列化
def deserialize_data(packed):
    values = struct.unpack('3if10s', packed)
    return {
        'a': values[0],
        'b': values[1],
        'c': values[2],
        'x': values[3],
        'y': values[4],
        'name': values[5].decode('utf-8').rstrip('\x00')
    }

data = {'a': 1, 'b': 2, 'c': 3, 'x': 1.5, 'y': 2.5, 'name': 'test'}
packed = serialize_data(data)
unpacked = deserialize_data(packed)

8.2 网络数据传输

import struct

# 构造网络数据包
def build_packet(seq_num, data):
    # 数据包格式:序列号(4字节) + 数据长度(4字节) + 数据
    packet = struct.pack('!II', seq_num, len(data))
    packet += data.encode('utf-8')
    return packet

# 解析网络数据包
def parse_packet(packet):
    seq_num = struct.unpack('!I', packet[:4])[0]
    data_len = struct.unpack('!I', packet[4:8])[0]
    data = packet[8:8+data_len].decode('utf-8')
    return {'seq_num': seq_num, 'data': data}

8.3 文件加密

# 简单的XOR加密
def xor_encrypt(data, key):
    encrypted = bytearray()
    for byte in data:
        encrypted.append(byte ^ key)
    return bytes(encrypted)

# 加密文件
def encrypt_file(input_file, output_file, key):
    with open(input_file, 'rb') as f:
        data = f.read()
    encrypted = xor_encrypt(data, key)
    with open(output_file, 'wb') as f:
        f.write(encrypted)

# 解密文件
def decrypt_file(input_file, output_file, key):
    with open(input_file, 'rb') as f:
        data = f.read()
    decrypted = xor_encrypt(data, key)
    with open(output_file, 'wb') as f:
        f.write(decrypted)

九、二进制文件处理技巧

9.1 分块读取大文件

def process_large_file(filename, chunk_size=4096):
    with open(filename, 'rb') as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            # 处理每个块
            process(chunk)

9.2 查找二进制模式

def find_pattern(filename, pattern):
    with open(filename, 'rb') as f:
        data = f.read()
        index = data.find(pattern)
        if index != -1:
            print(f"找到模式,位置:{index}")
        else:
            print("未找到模式")

find_pattern('file.bin', b'\x00\x01\x02')

9.3 修改二进制文件

def modify_file(filename, offset, new_data):
    with open(filename, 'r+b') as f:
        f.seek(offset)
        f.write(new_data)

# 修改文件的第10个字节
modify_file('file.bin', 10, b'\xFF')

十、最佳实践

10.1 使用with语句

# 推荐
with open('file.bin', 'rb') as f:
    data = f.read()

# 不推荐
f = open('file.bin', 'rb')
data = f.read()
f.close()

10.2 处理大文件时分块读取

# 推荐
with open('large_file.bin', 'rb') as f:
    while True:
        chunk = f.read(4096)
        if not chunk:
            break
        process(chunk)

# 不推荐(可能内存溢出)
with open('large_file.bin', 'rb') as f:
    data = f.read()  # 一次性读取整个文件

10.3 使用struct模块处理结构化数据

import struct

# 推荐:使用struct模块
data = struct.pack('if', 42, 3.14)

# 不推荐:手动构造
data = bytes([42, 0, 0, 0]) + bytes([0x1f, 0x85, 0xeb, 0x51])

10.4 异常处理

try:
    with open('file.bin', 'rb') as f:
        data = f.read()
except FileNotFoundError:
    print('文件不存在')
except PermissionError:
    print('没有权限访问文件')
except Exception as e:
    print(f'发生错误:{e}')

十一、总结

本集学习了二进制文件操作的知识:

  • 二进制文件与文本文件的区别
  • 二进制文件的打开、读取、写入操作
  • struct模块的使用方法
  • 图片、音频等二进制文件的处理
  • 二进制文件的应用场景和处理技巧

二进制文件操作在处理图片、音频、视频、网络数据传输等场景中非常重要。下一集我们将学习CSV文件操作。

十二、练习题

  1. 创建一个二进制文件,写入一些字节,然后读取并显示。
  2. 使用struct模块打包和解包一组数据(整数、浮点数、字符串)。
  3. 编写一个函数,复制图片文件。
  4. 实现一个简单的XOR加密/解密程序。
  5. 读取WAV文件的头部信息,显示采样率、声道数等。
  6. 编写一个函数,在二进制文件中查找指定的字节模式。
  7. 实现一个数据序列化/反序列化程序。
  8. 编写一个程序,分块读取大文件并统计文件大小。
« 上一篇 文本文件操作 下一篇 » CSV文件操作