整個資料夾拖進來,候選池只多了 1
把裝著 2558 個檔案的目錄拖到頁面中間的拖放區,鬆手後池中的數字只加 1:磁碟上排著 2558 個檔案,進池的是 1 個名字。
這一頁從頭到尾只處理名字,而檔案隨機抽取的名字口徑剛好解釋了目錄、同名、大小、數量上限四處容易踩空的地方。
代碼層先給答案:六個目錄 API 各 0 次命中
459 行的模板中,showDirectoryPicker、webkitdirectory、webkitGetAsEntry、getAsFileSystemHandle、dataTransfer.items、webkitRelativePath 這六個 API 的命中都是 0。唯一的檔案入口是 L204 那個只帶 multiple 的 <input type="file">,change(L301)與 drop(L299)都匯進 handleFiles(L303-311),而它對每個條目只讀一句 f.name(L306)。多選、drop 檔案、drop 目錄,三條入口在實作上是同一條,差別只在瀏覽器把什麼塞進 FileList。
真把目錄交給 input,只拿到一個空殼
Chrome 給 L204 的 FileList 是 len:1 / sizeSum:0,池中只多出一個空殼名字 tmp_dir_633;drop 一個 type 為空串的目錄條目同樣只入池 1 項,名字是 項目資料2026。兩種路徑都停在「一個名字」,沒有任何一層會去翻目錄底下的檔案。
有一條邊界要說清楚:showDirectoryPicker() 在無頭環境中的真實回傳是 SecurityError,message 原文含 Must be handling a user gesture to show a file picker,探針中 typeof 是 function、isSecureContext 是 true;從 JS 設上 webkitdirectory = true 再用 CDP 注入目錄,三次都是 setFileInputFiles 回傳 ok 而擷取到的 change 陣列為空、池恆為 0。這兩條是無頭環境的實測限制(不可觸發),不能寫成「瀏覽器不支援讀目錄」。本頁結論只能停在代碼層:六個 API 各 0 次命中,真實用戶在這條入口上只有 multiple 多選檔案。
能吃進多少:沒有硬上限
2558 個真實磁碟檔案一次注入,池 2558、預覽 35748 字元、handleFiles + updatePool 全鏈 413.1ms。合成 DataTransfer 派發真 drop,規模再大也全量入池:
| 條目規模 | 模板側耗時 |
|---|---|
| 1000 | 1 ms |
| 20000 | 16 ms |
| 50000 | 65 ms |
50000 條只花 65 ms,代價落在可讀性:預覽 74630px 對容器 120px,螢幕上只剩 0.16%。
圖 1 柱長按毫秒等比。
名字進池,位元組留在原地
.size、.type、FileReader、Blob、createObjectURL、fetch、accept 各 0 次命中,所以 10485760 位元組的 big_10mb.bin 與 33 位元組的 file_0001.txt 在池中完全等價。頁面也沒有「類型不支援」「檔案過大」這兩條失敗路徑,連 .rfile-empty 空態樣式(L160-168)都沒被任何元素引用。注入完成後 L301 隨即清空 input.value,檔案權杖當場丟棄,池裡只剩字串。
同一個目錄中,名字會撞車
磁碟實測有 500 組同名檔案,每組各佔 2 份:池是 2558 個槽位,不同名字只有 2058 個,同名佔掉 1000/2558 = 39.1% 的槽位。2558 池、count=1、開關關著連跑 2000 輪,按槽位加總的命中是 2795 次而實際只抽了 2000 次,多出的 795 次(39.8%)全落在同名名字上——重名檔案的中籤率是單名的 2 倍,而結果與複製串都只有裸名字,看不出抽中哪一份。
不放回去重的是槽位不是名字(L411-415):真實 2558 池、count=20 連抽 100 輪,有 4 輪在同一輪抽出兩個一模一樣的檔名,count=5 抽 400 輪也有 1 輪。跨輪完全獨立:池 5 項 count=2 連抽 3 輪,第 2、3 輪都出現 delta.txt;L418 的整體覆蓋讓統計格只讀最後一輪的條數(L352),不是累計值。
圖 2 柱長按項數等比。
數量框只有 min,沒有 max
L224 的抽取數量框只寫了 min="1"。L367 的 parseInt(v)||1 把 0 / abc / 1000 / 1e3 / 0x10 全靜默當 1,2.7 當 2。全模板 validity、checkValidity、reportValidity 各 0 次,會中斷抽取的只有 3 條 toast:候選池為空(L363)、抽取數量至少為 1(L369)、不能超過候選數(L374)。填 100000 時頁面真跑:牆鐘 112291ms、300070 個頁面元素、複製串 1397501 字元。
隨機源是 Math.random 的 3 個呼叫點:動畫(L390)、放回(L406)、洗牌(L412),crypto、getRandomValues、seed 各 0 次,給不出可重現序列。不放回抽 1 個也要洗完 2558 項;而 rfileRolling 寫 2 次讀 0 次(L379、L399),動畫期間再點不會被攔。
匯出只有一個字串
copyResults()(L442-445)交給 UU.copy 的就一個字串:純 LF 分隔,無序號、無表頭、無末行換行。螢幕上序號排到 1..7,複製出來只有 7 行裸名字,1000 項是 3999 字元。app.js 的 L37 與 L53 兩條分支彈同一句「已複製到剪貼簿」,兜底失敗也看到成功提示;download 命中 0 次,沒有匯出檔案這條路。
對照 config/tools.php L1513-1525 的 14 條宣稱,[衝突] 9 條中就有 intro 那句「不重複:每個檔案只被抽取一次」——同名那一節直接把它推翻。
三個常被問到的邊界
Q:這一頁能不能把整個資料夾讀進來?
代碼層不能:六個目錄 API 各 0 次命中,drop 目錄和把目錄交給 input 都只入池 1 個名字。
Q:同名檔案算一個候選還是兩個?
算兩個槽位、各有一次機會,中籤率是單名的 2 倍,但結果看不出抽中哪一份。期望算術轉盤那篇舊文推過,本篇只給口徑。
Q:能按大小或類型先篩一遍嗎?
模板從不讀位元組,10485760 與 33 等價,既沒有篩選也沒有按權重的抽法——weight 的命中全是 CSS 字重。