去年冬天我帮人从一台老机器上取一年的接口访问日志,落成 37 MB 的 txt。拷回笔记本双击,记事本转了好几秒光标才亮,换个编辑器又直接弹提示问要不要继续打开。更要命的是发给外部对账,压成 zip 做附件发出去被退回来,理由超出大小限制。那天我才承认一件事:文本文件的麻烦通常不在内容,而在体积,而且"打得开""发得出""看得清"是三件需要分开解决的事。
先分清是哪里在卡
我现在的习惯是先把卡住的位置定位清楚,因为三处的解法不一样。
编辑器卡,是内存和排版的问题。整份文件进了缓冲区,可视区域还要把几十万个字符重新排版,这种卡不是再等一会儿就好,是每按一次方向键都要重排一次。
传输卡,是链路上限的问题。这里有个我吃过亏的细节:附件在邮件里要走 Base64,编完的体积约是原文的 4/3,多出三分之一。25 MB 的上限反推回去,原文超过 18 MB 就贴着边了,18 MB 编完正好 24 MB。所以我现在算附件是先算编码后的数字,不看文件属性里那个。
处理卡,是"这份文件不是给我一个人看"。接手的人可能只关心某几个小时,或者要把它塞进一个一次只吃小文件的脚本,这时切分不是为了传输,是为了让每一步的输入规模可控。
三处共同的解法都是切成几份,但切口选在哪里,决定后面省不省心。
三种切法的取舍
我在 文本切割 里能选的就三种口径:按行数、按大小、按分隔符,另外可以自己定文件名前缀和序号规则,切完打包下来。这三种我踩过不同的坑,先把结论摆前面:我默认先试按行数,其次按分隔符,按大小是万不得已。
| 切法 | 切口落在哪 | 每份体积可控吗 | 会不会截断字符 | 我常用在它做什么 |
|---|---|---|---|---|
| 按行数 | 换行符外面,每份固定 N 行 | 不直接控,随行长短浮动 | 不会,字符完整 | 日志、逐行导出的数据 |
| 按大小 | 到达指定 KB/MB 的位置 | 可控,这是它唯一的优势 | 可能,见下一节 | 上游有硬性体积上限时 |
| 按分隔符 | 遇到指定标记(空行或固定行) | 不可控,块有大有小 | 不会 | 分章节的文档、带结束标记的导出 |
按行数的好处是每条记录都是完整的一条,接手的人不必判断"这半行是谁的尾巴"。那份日志共 48 万行,按每份 2 万行切正好 24 份,每份 1.5 MB 上下,编辑器打开不用等。
按分隔符适合"语义块比行数重要"的场景。我遇到过一份内部导出,每条记录末尾固定带一行 -- END --,按这个标记切,每份都是完整记录的集合。缺点是份数不由你定,一个块特别大时就压不平。
按大小切只在一种时候值得用:目标就是"每份不超过某个数",比如某个接口对请求体有上限。代价是它不保证切口落在行边界,也不保证落在字符边界。
按字节切真正会踩的坑
这个坑要说准确:不是所有编码都会被截断,只有"一个字符占多个字节"的编码才有这个风险。
UTF-8 里英文字母、数字、常见 ASCII 标点是一个字符 1 字节;中文常用汉字是 3 字节。"中"这个字码点 U+4E2D,在 UTF-8 里存成 E4 B8 AD 三个字节。GBK 里汉字是 2 字节,UTF-16LE 里连英文字母都是 2 字节。所以如果切分点正好落在 E4 B8 和 AD 中间,前一个文件末尾留下两个残缺字节,后一个文件开头多出一个孤立字节,两边都不构成合法字符。
用 UTF-8 去解码这两段,轻则显示成问号或替换字符,重则那个残缺字节会和后一段的开头字节"凑"成一个合法但完全无关的汉字,内容悄悄变了,肉眼很难发现。反过来说,如果文件从头到尾是纯 ASCII,每个字符都只占 1 字节,按字节怎么切都不会截断——这就是"我上次切英文日志什么事都没有"不能推广到中文文件上的原因。
顺带分清一件不是截断但常被误当成截断的事:编码不一致。GBK 存的中文被当成 UTF-8 去读,整篇都是乱码,但一个字节都没少,换个编码再打开就恢复正常。真要排查这类问题,我一般会先用 文件信息读取 把每份的大小和时间批量看一眼,确认没有 0 字节的半成品混在里面。
切完做一次守恒校验
我固定比三样:行数、字节数、首末行。
行数:各份行数相加应等于原文件行数,前提是按行数切且原文件末尾没有多余空行。字节数:相加通常会差一点,因为切口处可能补了换行,或者文件头的 BOM 只保留在第一份,我会算一个差值,超出预期再回去查。首末行:第一份的第一行和最后一份的最后一行,必须和原文件的第一行、最后一行完全一致。
换行符的差值不值钱,但会干扰校验:48 万行的文件,切的时候如果把两字节的 Windows 换行统一成一字节,光这一项就少 480000 字节,约 0.46 MiB,你的"字节数守恒"就永远对不上。所以我建议切之前先确认原文件用的是哪种换行。
最后一道手段是合回去自证:按文件名顺序用 文本合并 合一遍,行数和字节数和原文件对得上,才能说切口没吃掉内容。我做过三次,其中一次就是靠它发现末尾少了一行。
两个小细节,踩过才知道
序号位数要补齐。part_1.txt 到 part_24.txt 按名称排序时,part_10.txt 会排在 part_2.txt 前面,因为是逐字符比。我后来一律补成 part_01.txt 这样的两位或三位。
切完的编码要和切之前一致。中文文件我 UTF-8 一路走到底,不在中途改,也不在合并前"顺手"转一次;两份编码不同,合出来的文件后半段一定出问题。
常见问题
Q:几十 MB 的 txt 一定要切开才能看吗?
如果只是自己找几个关键字,不切也能对付;一旦要给别人读、贴进工单、逐段核对,切开比反复滚动可靠。我的判断线是:需要两个人以上读这份文本,就切。
Q:按大小切和按行数切,我该选哪个?
看谁在消费这份文件。消费方是人和编辑器,选按行数,行完整比体积均匀重要;消费方是接口或有硬上限的存储,选按大小,然后额外检查每份的末尾有没有把中文字切坏。
Q:切完中文变成问号,是被切坏了吗?
不一定。先看是不是整份都乱,而不是只有接缝处乱。整份乱通常是编码读错了,换一种编码再打开;只有接缝处那一两个字符乱,才是按字节切把多字节字符截断了,这种只能重切,改按行数或按分隔符。
Q:CSV 能按行数切吗,表头怎么办?
能,但表头要自己补。我切完会给每份补一行同样的表头,否则从第二份开始列义就丢了;另外如果某行里有被引号包住又含换行的字段,按行数切会把一条记录劈成两截,这种得先确认有没有跨行字段。
Q:切完还要合回去,是不是白干一场?
不是。切是为了让每一步的输入规模可控,合是为了校验有没有丢内容,两个目的不同。真要长期在切开的文件上干活,我会留一份原文件不动,切出来的只当副本。
回到那个 37 MB 的日志。最后是 48 万行按每 2 万行切成 24 份,序号补成两位,编码保持 UTF-8 不动,附件问题也一起解决了——每份 1.5 MB,编码后离上限还远。要问我怎么定的切法,其实就一句话:先问这份文件下一步会被谁、用什么方式读,再决定切口落在换行、标记还是字节上。