去年冬天我幫人從一台老機器上取了一年的介面存取日誌,存成 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,編碼後離上限還遠。要問我怎麼定的切法,其實就一句話:先問這份檔案下一步會被誰、用什麼方式讀,再決定切口落在換行、標記還是位元組上。