雜記 › 技術筆記 › 文字切成 43 片再拼回去少了 42 個位元組,切口其實都落在換行上

文字切成 43 片再拼回去少了 42 個位元組,切口其實都落在換行上

300 行文字按每份 7 行切出 43 片,拼回去比原檔案少 42 個位元組,這些位元組全落在切點的換行上;漢字不會被切成兩半,但每份 0.1 會擊穿預算、片首空行會被吞。

切口在換行上,不在位元組裡

手上有一份 300 行的純文字、一共 2289 位元組,按每份 7 行切下去,頁面報「已切割為 43 份」。把 43 個分片按順序首尾相接拼回去,再和原始檔案逐位元組比,比原文少了 42 個位元組。這 42 個位元組沒丟在打包、也沒丟在編碼,全部落在切口上——是夾在每兩片之間的那個換行被吃掉了。

先拆掉最容易誤會的一條:這一頁的按大小分支並不是真按位元組硬切。它先把整段文字按換行拆成行陣列,再一行一行往預算裡累加,湊夠就收尾成一片,切點只能落在換行符上,所以一個 3 位元組的漢字在結構上就不可能被切成兩半。把模板裡的切割函數原樣抽出來跑,240 組混合輸入分別過三種模式、共 720 次切割、1717 個分片,片裡出現替換字元 U+FFFD 的分片 0 片,被切成半字的分片也是 0 片。

一組決定性樣本最能說明問題:輸入是 100 個「測」加 1 個換行再加 100 個「測」,合起來 201 個碼元、601 位元組(每個「測」3 位元組)。每份大小填 0.4,程式把預算取成 409 位元組,切出 2 片、每片正好 100 碼元、300 位元組;兩片的頭 2 位元組都是 E6 B5、尾 2 位元組都是 B5 8B,兩邊都是完整 UTF-8,少的只是中間那 1 個換行。真要拿這一頁切中文長文,文字切割 這條按行累加的路子對多位元組字元是安全的。

line 與 size:切點上的換行去了哪

切點恆在換行符上,就有如下幾乎每次都成立的等式:按行數、按大小這兩種模式,各片位元組合計等於原位元組減去(片數減 1)。回到開頭那份檔案,2289 位元組切 43 片,代入得 2247,和拼回去量到的分片合計對得上;中文樣本 601 位元組切 2 片,也剛好少 1。

順著這條等式能看出預算怎麼被整行擊穿:每份大小取 0.1 時預算無條件捨去成 102 位元組,而單行本身就是 300 位元組,預算比一行還小。程式不切行也不報錯,把每行單獨封成一片,於是 300 行、每行 100 個「測」(合計 90299 位元組)切成 300 片、每片 300 位元組,分片合計 90000、比原文少 299。把 1000 個「測」擠在一行、每份大小填 1,也只得 1 片 3000 位元組。所謂按大小,只保證切點落在整行邊界,不保證每片真不超過那個數。

還有一種更隱蔽的吞位元組:按大小分支在緩衝區為空時把當前行直接賦值,於是每片開頭的空行被跳過。輸入「兩個空行加 abc 加換行加 def」共 9 位元組,切完只剩 1 片、內容 7 位元組,缺口對不上那條等式,因為丟的是行本身而非切點上的換行。

原始檔案2289 B分片合計2247 B切口換行42 B300 行按每份 7 行切 43 片

圖 1:2289 位元組切 43 片、分片合計 2247 位元組,差的 42 個位元組全在切點上的換行裡。

模式切口落在片合計與原位元組之差樣本
按行數換行符片數減 12289 切 43 片少 42
按大小換行符片數減 190299 切 300 片少 299
按分隔符號分隔符號本身分隔符號寬乘(片數減 1)53 切 4 片少 9

delim:分隔符號本身不進任何一片

按分隔符號和行、位元組都無關,它拿分隔符號去切,分隔符號被切掉後兩片都不含它,損失換成另一條等式。有一份 53 位元組的樣本,用三個等號串 === 當分隔符號切 4 片少 9 位元組;換成兩個換行當分隔符號切 3 片,少的是 4 位元組。拿原來的分隔符號把分片重新接回去,這幾組都能逐位元組還原,和行數、大小模式吃換行是同一類帳。

拼回:18 組裡只有 3 組直接相等

用 18 組固定樣本(6 種輸入乘 3 種模式)做逐位元組比較:分片直接首尾相接、什麼都不補,只有 3 組和原輸入相等,其餘 15 組差值恰好等於各自切點數。補回 1 個切點分隔符號再比就有 17 組相等,唯一補不回來的正是上面 size 吞首部空行那組。放大到 720 次批次:空字串串接相等 360 組、補分隔符號後相等 688 組,分模式看行數與分隔符號各 240 組、大小只有 208 組,那 32 組即使補分隔符號也對不上。要複核接縫,我習慣拿兩份文字直接跑一次文字對比,一眼看出差的是換行還是內容。

頁面上的位元組數未必等於真實位元組

算帳前先確認尺子。這一頁估大小是逐碼元分三檔:一個「測」算 3、全形逗號算 3、2 位元組字元算 2、帶 BOM 算 3,都和標準編碼對得上;只有增補平面字元算錯——它的兩半被當成兩個 3 位元組各算一遍,一個表情符號會報成 6 位元組而真值是 4,每個碼位固定高報 2。一片 150 個這類字元的內容介面顯示 900 B,落到檔案真實 600 位元組。要核對碼元與位元組,可以先用字數統計 對一遍再比這一頁的口徑。

顯示單位走 1024 進位制、KB 與 MB 檔保留兩位小數:1023 還是 1023 B,到 1024 才跳成 1.00 KB;填 100000 顯示 97.66 KB,而按十進位這本來是 100 kB。拼回時要用真實位元組,別拿這幾個顯示值去減。

檔名也不補零:前綴直接接序號再接副檔名,切到 1000 片時序號位數是 1 位 9 個、2 位 90 個、3 位 900 個、4 位 1 個,按字典序最前四個依次是 split_1.txt、split_10.txt、split_100.txt、split_1000.txt;介面片號補到 3 位顯示成 001,只影響徽章不進檔名。參數填錯時,每份行數取 0、留空、負數或字母都報「請輸入有效行數」,每份大小那幾類報「請輸入有效大小」,分隔符號轉義後為空報「請輸入分隔符」,輸入框空報「請輸入文本」;而每份行數取 301 或 1000(都大於總行數 300)不報錯、只出 1 片。

對照實驗:真按位元組硬切會怎樣

這條只為把風險和本頁實際行為分開。拿 6 個「測」共 18 位元組,若硬按位元組在第 4 個位置切斷,左半拿去解碼會得到 1 個完整漢字再加 1 個替換字元 U+FFFD,用嚴格模式解碼直接丟出型別錯誤。而本頁按大小分支按行累加,根本不產生切在碼元中間的片段——720 次切割裡半字分片 0 片就是這條對照的落點。所以拼回去少的位元組要算在換行和分隔符號頭上,而不是算成漢字被切壞。

常見問題

Q:按大小切是不是就能保證每片不超過我填的數?

不能。預算按整行累加比較,遇到單行已經超過預算(每份填 0.1、預算 102 位元組,一行卻有 300 位元組)時不截斷,只一行封一片,片合計 90000 對原文 90299 少的還是那 299 個換行。

Q:切完拼回去比原始檔案少位元組,是不是丟內容了?

多數少的只是切點上一個換行或一個分隔符號。補回對應分隔符號,18 組裡 17 組能逐位元組還原;對不上那組是 size 吞了片首空行,得回去查輸入開頭有沒有空行。

Q:介面顯示的大小能直接拿去算位元組帳嗎?

不建議。增補平面字元每個碼位固定多算 2,單位又是 1024 進位制帶兩位小數,核對請用真實位元組口徑。

📎 相關文章

貼上 100 行分數,第 45 行被悄悄算成 1/4:約分這頁的負號、空行和大數各歸誰管 2026-11-03 把 3. 起步貼進 Markdown 轉 HTML,為什麼有序列表的編號只認第一個數 2026-11-03 Crontab 表達式 0 0 13 * 5 到底哪天跑:這一頁只排 2 次,真實 cron 是 62 次 2026-11-03 16 組開關實測 HTML 去標籤:報 19 個標籤,實際刪掉 148 個字元 2026-10-30 SVG 轉 PNG 後外部引用的圖丟了一半:同一份 594 B 檔案預覽有 3 張圖、成品只剩 1 張 2026-10-30