You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

按行和字节拆分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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 06:35:37