切點寫死在最後一個點:archive.tar.gz 只分到 gz
手邊攢了一堆檔名想按後綴歸堆:archive.tar.gz 到底算 gz 還是 tar.gz?photo. 算不算有後綴?開頭帶點的 .gitignore 又該進哪一列?把這幾行貼進去、點一次「拆分後綴」,結果和腦子裡那個「擴展名」並不完全對得上。差的不是按鈕,是切點被釘死在最後一個點,外加緊跟其後的一條分支把兩種輸入並到了一起。模板一共 6546 位元組 / 158 行,拆後綴這件事全住在 splitSuffix 那十來行裡。
切點走的是 filename.lastIndexOf('.'),從右往左數到的第一個點就是唯一切點。檔名後綴拆分於是把 archive.tar.gz 切成 name 為 archive.tar、ext 為 gz——多重後綴永遠只切最後那一段。11 個樣本裡點數 ≥ 2 的有 7 格,切完之後 ext 裡含點的 0 格、name 裡仍帶點的反而有 8 格:中間那些點全被留在了檔名那一側,而不是跟著進擴展名列。
圖 1:切點釘在最後一個點,多重點全被留在檔名側。
dot <= 0 這一條,把「沒有點」和「點打頭」並成一個返回
緊跟著切點的 L66 那道 if (dot <= 0) 一次接住兩類完全不同的輸入:dot 為 -1(README 根本沒有點)和 dot 為 0(.gitignore 的點就在第 0 位)。兩者共用同一個 return { name: filename, ext: '' }——整串原樣進 name,ext 給空字串。於是 .gitignore 不被拆出擴展名、README 也一樣,兩格落進同一個「無後綴」計數。
真正容易被讀漏的是尾點那一族:photo.、.hidden.、..、name.。它們走的是 dot > 0 那條分支,點確實在串裡某處,只是 substring(dot + 1) 從點後一位切到串尾,切出來是個空字串。於是 ext 照樣為空,在 L96 的統計裡它和 README、.gitignore 一起落進 withoutExt——11 格裡這樣的有 4 格。差別只落在 name:前兩類整串進 name,尾點族的 name 被削掉了最後那個點。
逐格攤開:11 行真值表,看第一列對不回輸入的有哪幾格
下面把 11 個樣本一格一格鋪開。最後一列是「重建出來的第一列」,它等於 name 拼回 ext;拿它和修剪後的輸入逐格比,7 格對得上、4 格對不上,對不上的正是 photo. / .hidden. / .. / name. 這四個尾點樣本。
| 輸入檔名 | 最後一個點 | name | ext | 第一列重建串 | 對得上? |
|---|---|---|---|---|---|
| archive.tar.gz | 11 | archive.tar | gz | archive.tar.gz | 是 |
| my.file.txt | 7 | my.file | txt | my.file.txt | 是 |
| .gitignore | 0 | .gitignore | — | .gitignore | 是 |
| .bashrc.conf | 7 | .bashrc | conf | .bashrc.conf | 是 |
| README | -1 | README | — | README | 是 |
| photo. | 5 | photo | — | photo | 否 |
| .hidden. | 7 | .hidden | — | .hidden | 否 |
| a..b | 2 | a. | b | a..b | 是 |
| .. | 1 | . | — | . | 否 |
| name. | 4 | name | — | name | 否 |
| 多.重.後.綴.zip | 7 | 多.重.後.綴 | zip | 多.重.後.綴.zip | 是 |
重建出來的那一列,會悄悄吃掉一個尾點
第一列不是原樣回填輸入,它是按 name + (ext ? '.' + ext : '') 現拼的重建串,而輸入在進函式之前已經被剪過首尾空白。photo. 走完一圈,name 是 photo、ext 空,重建串就停在 photo,那個尾點沒了。尾點這一族通吃:三個點 ... 重建剩 ..,archive.tar.gz 後面再補一個點,回來變回 archive.tar.gz——每次恰好吞掉 1 個尾點。第二列的 name 卻仍保留中間那些點,所以兩列對不齊的根子在重建那一步,不在切分本身。
本頁所有樣本都按輸入原樣大小寫記著,後綴的大小寫歸後綴大小寫轉換那頁管。
圖 2:重建列每次吞掉一個尾點,11 格裡 4 格對不回輸入。
統計三行和提示語,是同一本賬
右上角的統計走 L96 的 if (r.ext),判據是 ext 空不空,不是「有沒有點」。11 個樣本整批跑一遍,total 11、有後綴 5、無後綴 6,和表格的 11 行、6 個破折號格、5 個綠格、提示語那句「已拆分 11 個檔名」完全同分母,5 + 6 = 11 也咬得上。點「示例」灌進去的那 6 行則得 6 / 4 / 2,每行點數分別 1、2、0、1、1、1,archive.tar.gz 那一格一個人貢獻 2 個點、ext 卻只拿走 gz。
空行只進分母、不進任何計數:摻進 20000 行裡 5000 個空行,實測統計停在 15000,被跳過的 5000 不出現在任何一列。同分母這三處對齊,可以逐條核:
- 表格行 11、破折號格 6、綠格 5,提示語的 N 也寫 11,四處同分母。
- 示例灌的 6 行得 6 / 4 / 2,且 6 = 4 + 2。
- 20000 行摻 5000 空行,統計 15000,空行不進任何一列。
至於同工具更早那篇拆兩列的文章,量的是另一套賬,和這裡切點的分支不是一回事,它的結論本篇不重複;要看那條鏈路可點開檔名拆兩列的舊文。
Q:photo. 明明有個點,怎麼被算成無後綴?
點在倒數第一位,substring(dot + 1) 從點後一位切到串尾是空字串,ext 為空,統計就把它歸進無後綴那一格;只是它的 name 被削掉了尾點,和 README 那種整串進 name 並不一樣。
Q:.tar.gz 不能整體當擴展名嗎?
在這一頁不能。切點恆在最後一個點,11 格裡 ext 含點 0 格,.tar.gz 的 ext 只剩 gz,前面那段留在 name 裡——工具文案裡「支援多擴展名」這句,和程式碼實際切出來的並不一致。
Q:第一列和輸入對不上,是壞了嗎?
它是按 name 拼回 ext 現造的重建串,不回填原串。尾點這一族因此在第一列少一個點,11 格裡有 4 格對不回修剪後的輸入,而第二列的 name 仍保留中間那些點。