按行和字节拆分UTF-16-LE文本文件时的异常问题
问题描述
我有若干采用UTF-16-LE with BOM编码、大小10~50MB的文件,需要拆分为不超过1MB的小文件(例如File1.txt拆分为File1-part-0.txt、File1-part-1.txt等)。采用的拆分逻辑是:逐行读取文件,计算每行的字节大小,若拼接后总字节数不超过1MB则继续拼接;若添加当前行超过1MB,则将已拼接内容写入拆分文件,再用当前行覆盖拼接变量(避免拆分单词/句子导致翻译错误)。
但运行代码后出现两个问题:
- 仅第一个拆分文件小于1MB,其余文件大小均为1994KB左右
total_bytes变量计算出的字节数大于原文件大小,且存在数据丢失情况
现有代码
#!/usr/bin/python3 import os, sys MAXIMUM_FILESIZE = 1000000 #Maximum allowed filesize in bytes (1MB) FILETYPE = ".txt" #Filetypes (Example: ".txt") FILE_ENCODING = "utf-16-le" #File encoding (Example: "utf-16-le" or "utf-8") import_folder_path = r"path\to\import\folder" #Import folder path export_folder_path = r"path\to\export\folder" #Export folder path #Creates list of files in folder file_list = os.listdir(import_folder_path) #Reads data from each file in folder for file in file_list: #Creates absolute filepath for each file import_filepath = import_folder_path + "\\" + file #Reads metadata from file file_metadata = os.stat(import_filepath) current_filesize = file_metadata.st_size with open(import_filepath, "r", encoding=FILE_ENCODING) as input_file: #Reads every line in file lines = input_file.readlines() i = 0 #Initializer for split file counter #Creates new name for smaller files temp_file = file.rsplit(FILETYPE) new_filename = temp_file[0] + "-part-" + str(i) + FILETYPE export_filepath = export_folder_path + "\\" + new_filename total_bytes = "" #Initializer for reading data in bytes for line in lines: #Concatenates data and checks byte size if (int(len(total_bytes.encode(FILE_ENCODING))) + int(len(line.encode(FILE_ENCODING))) <= MAXIMUM_FILESIZE): total_bytes += line #Writes concatenated data to split file elif (int(len(total_bytes.encode(FILE_ENCODING))) + int(len(line.encode(FILE_ENCODING))) > MAXIMUM_FILESIZE): print(f"Size: \"{len(total_bytes.encode(FILE_ENCODING))}\"") with open(export_filepath, 'w', encoding=FILE_ENCODING) as output_file: output_file.write(total_bytes) i += 1 #Increments split file counter #Creates new filename for next split file temp_file = file.rsplit(FILETYPE) new_filename = temp_file[0] + "-part-" + str(i) + FILETYPE export_filepath = export_folder_path + "\\" + new_filename #Adds current row being read to overwrite variable data. This is #needed to prevent the current row of data being read from missing. total_bytes = line else: print("Error")
问题原因与修复方案
1. total_bytes字节数大于原文件的原因
- 换行符转换问题:Python文本模式读取时会将Windows的
\r\n转为\n,写入时又转回\r\n。原文件中\r\n占4字节(UTF-16-LE编码),读取后变为2字节的\n,写入时又变回4字节,导致总字节数增加。 - 重复编码计算误差:每次通过
len(total_bytes.encode(FILE_ENCODING))计算字节数,重复对已拼接字符串编码,效率低且易因特殊字符(如Unicode代理对)产生偏差。 - BOM未计入:原文件大小包含UTF-16-LE的BOM(2字节),但读取时BOM被自动跳过,
total_bytes编码结果不含BOM;若拆分文件写入时添加BOM,会进一步增加总字节数。
2. 拆分文件超过1MB的原因
- 未考虑BOM字节占用:若拆分文件需保留BOM,每个文件会额外占2字节,当拼接内容接近1MB时,加上BOM就会超出限制。
- 字节数计算逻辑缺陷:代码仅判断拼接后的编码字节数,但文本模式下的换行符转换会额外增加字节,导致实际文件大小超标。
3. 数据丢失的原因
循环结束后,total_bytes中剩余的最后一批内容未写入拆分文件,直接丢失。
修复后的代码
#!/usr/bin/python3 import os MAXIMUM_FILESIZE = 1000000 # 最大允许文件大小(字节,1MB) FILETYPE = ".txt" FILE_ENCODING = "utf-16-le" # 若需拆分文件保留BOM,将编码改为"utf-16",同时可将MAXIMUM_FILESIZE调整为999998(1MB-2字节) import_folder_path = r"path\to\import\folder" export_folder_path = r"path\to\export\folder" # 确保输出文件夹存在 os.makedirs(export_folder_path, exist_ok=True) # 仅处理目标类型文件 file_list = [f for f in os.listdir(import_folder_path) if f.endswith(FILETYPE)] for file in file_list: import_filepath = os.path.join(import_folder_path, file) base_name = os.path.splitext(file)[0] part_index = 0 current_content = [] current_byte_count = 0 with open(import_filepath, "r", encoding=FILE_ENCODING) as input_file: for line in input_file: # 预计算当前行的编码字节数,避免重复编码 line_byte_len = len(line.encode(FILE_ENCODING)) # 拼接后超限制且已有内容,写入文件 if current_byte_count + line_byte_len > MAXIMUM_FILESIZE and current_content: export_filename = f"{base_name}-part-{part_index}{FILETYPE}" export_filepath = os.path.join(export_folder_path, export_filename) with open(export_filepath, "w", encoding=FILE_ENCODING) as output_file: output_file.write(''.join(current_content)) # 重置计数器与内容 part_index += 1 current_content = [line] current_byte_count = line_byte_len else: current_content.append(line) current_byte_count += line_byte_len # 写入最后一批剩余内容 if current_content: export_filename = f"{base_name}-part-{part_index}{FILETYPE}" export_filepath = os.path.join(export_folder_path, export_filename) with open(export_filepath, "w", encoding=FILE_ENCODING) as output_file: output_file.write(''.join(current_content))
修复说明
- 预计算行字节数:提前计算每行编码后的字节数,避免重复对整段字符串编码,提升效率并减少误差。
- 处理剩余内容:循环结束后写入最后一批未完成的内容,避免数据丢失。
- 路径安全拼接:使用
os.path.join替代手动拼接路径,解决跨平台路径兼容问题。 - BOM可选支持:若需拆分文件保留BOM,将编码改为
utf-16,Python会自动为每个拆分文件添加BOM,同时可调整最大文件大小预留BOM的2字节空间。
内容的提问来源于stack exchange,提问作者Andy Garcia
相关产品推荐
相关产品推荐

