使用lz4.frame压缩多文件时解压出现UnicodeDecodeError问题求助
问题现象
使用lz4.frame压缩多文件并写入元数据(文件名长度、文件名、文件大小)后,第一个文件可正常解压,但第二个文件读取到的文件名长度错误(变为29742,实际应为9),进而触发UnicodeDecodeError——因为读取的内容包含了压缩数据而非文件名。
压缩输出:
file name length 9
file name file1.txt
file size 762
file name length 9
file name file2.txt
file size 1105
file name length 9
file name file3.txt
file size 1472
解压输出:
file name length 9
file name file1.txt
file size 762
file name length 29742
Traceback (most recent call last):
File "d:\Code Playground\Python\fileSharing\fileSharing (2).py", line 250, in
decompression("archive.lz4")
File "d:\Code Playground\Python\fileSharing\fileSharing (2).py", line 166, in decompression
file_name = archive.read(file_name_length).decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc5 in position 167: invalid continuation byte
核心原因
压缩时未记录压缩后数据的长度,解压阶段无法确定当前文件的压缩数据何时结束,错误地使用archive.read()读取所有剩余内容(包含下一个文件的元数据)作为当前文件的压缩数据。解压后指针未正确定位到下一个文件的元数据起始位置,导致后续读取的元数据是压缩数据的一部分,解析出错误的文件名长度。
解决方案
1. 修改压缩逻辑:添加压缩数据长度记录
在写入原始文件大小后,追加写入压缩后数据的长度,确保解压时能精确读取对应长度的压缩数据。
修改后的压缩代码:
import os import struct import lz4.frame def compression(file_paths, archive_path): with open(archive_path, 'wb') as archive: for file_path in file_paths: file_name = os.path.basename(file_path) file_name_bytes = file_name.encode('utf-8') file_name_length = len(file_name_bytes) # 写入文件名长度(指定小端字节序,跨平台兼容) archive.write(struct.pack('<i', file_name_length)) # 写入文件名 archive.write(file_name_bytes) # 读取原始文件并压缩 with open(file_path, 'rb') as f: raw_data = f.read() compressed_data = lz4.frame.compress(raw_data) raw_size = len(raw_data) compressed_size = len(compressed_data) # 写入原始文件大小 archive.write(struct.pack('<i', raw_size)) # 写入压缩后数据长度 archive.write(struct.pack('<i', compressed_size)) # 写入压缩数据 archive.write(compressed_data) # 打印日志(可选) print(f"file name length {file_name_length}") print(f"file name {file_name}") print(f"file size {raw_size}")
2. 修改解压逻辑:按记录的长度读取压缩数据
读取完原始文件大小后,先读取压缩数据长度,再读取对应长度的字节作为压缩数据,确保指针精确移动到下一个文件的元数据位置。
修改后的解压代码:
import struct import lz4.frame def decompression(archive_path): with open(archive_path, 'rb') as archive: while True: # 读取文件名长度 fname_len_data = archive.read(4) if not fname_len_data: break # 归档文件读取完毕 file_name_length = struct.unpack('<i', fname_len_data)[0] # 读取文件名 file_name = archive.read(file_name_length).decode('utf-8') # 读取原始文件大小 raw_size_data = archive.read(4) raw_size = struct.unpack('<i', raw_size_data)[0] # 读取压缩数据长度 comp_size_data = archive.read(4) compressed_size = struct.unpack('<i', comp_size_data)[0] # 打印日志(可选) print(f"file name length {file_name_length}") print(f"file name {file_name}") print(f"file size {raw_size}") # 读取指定长度的压缩数据 compressed_data = archive.read(compressed_size) # 解压数据 decompressed_data = lz4.frame.decompress(compressed_data) # 校验解压后数据长度(可选,增强可靠性) if len(decompressed_data) != raw_size: print(f"警告:{file_name}解压后长度与原始大小不符") # 写入输出文件 with open(file_name, 'wb') as output_file: output_file.write(decompressed_data)
额外注意事项
- 使用
<i指定小端字节序,避免不同平台(如Windows/Linux)间的整数解析错误 - 用
len(raw_data)获取原始文件大小,比os.path.getsize更可靠,避免文件读取前被修改导致的不一致 - 可选添加解压后长度校验,及时发现数据损坏或归档错误
内容的提问来源于stack exchange,提问作者Sajjad Ali

