:.NET多格式壓縮解壓與避坑指南)
簡介SharpCompress 0.37.2 為面向 .NET 開發(fā)者的跨版本壓縮解壓庫資源適用于在 C# 項目中便捷處理 zip、tar、7z 等常見歸檔格式可有效降低文件流讀寫與壓縮算法集成的復(fù)雜度。該壓縮包共包含 11 個文件以針對 net8.0、net6.0、netstandard2.1、netstandard2.0 及 net462 等目標(biāo)框架編譯的 SharpCompress.dll 為核心同時附帶 XML 文檔、NuGet 包描述文件、程序集簽名及 README 說明便于開發(fā)者按項目環(huán)境選取引用并快速查閱 API 用法。整個資源大小約 1.19MB輕量實用。已有 87 人下載學(xué)習(xí)適合需要引入成熟壓縮方案的中高級 .NET 工程師或需要在離線場景集成庫文件的項目團(tuán)隊。通過該壓縮包可直接獲取對應(yīng)版本的托管程序集、包元數(shù)據(jù)與文檔免去在線安裝的額外步驟同時核驗包簽名信息有助于提升構(gòu)建與交付的可靠性。1. SharpCompress 是什么解壓界的瑞士軍刀但別被 0.37.2 這個版本號騙了如果你在 .NET 項目里被 rar、7z、tar.gz 這些格式搞得焦頭爛額sharpcompress.0.37.2 這個 NuGet 包應(yīng)該在你的還原清單里。它不是微軟官方組件卻在開源社區(qū)里被廣泛當(dāng)作多格式壓縮的默認(rèn)選擇。這套 0.37.2 版本體積不大但把 zip、tar、rar、7z 的讀寫統(tǒng)一在了一套流式 API 里寫起來比反復(fù)切換底層命令要順手得多。這篇筆記從選型理由講到踩坑記錄適合剛接手帶歷史包袱的壓縮模塊、或者想給工具箱補(bǔ)一個多格式庫的 .NET 開發(fā)者。需要提醒的是這個版本號自帶一些歷史含義后面我會單獨說明。2. 為什么 SharpCompress 能一次搞定 zip、rar、7z格式支持與選型邏輯2.1 SharpCompress 與 System.IO.Compression 的邊界什么時候該換庫很多項目一開始用 System.IO.Compression 處理 zip發(fā)現(xiàn)也能跑但需求一復(fù)雜就卡住。微軟官方庫只內(nèi)置了 zip、gzip、tar 的一部分rar 和 7z 完全不在支持范圍。如果有人給你一個加密的 rar 分卷包官方庫只能干瞪眼。SharpCompress 的定位就是補(bǔ)上這些缺位它同時支持 zip、tar、tar.gz、tar.bz2、rar、7z、gzip、bzip2、xz 的讀取以及 zip、tar、gzip、bzip2 的寫入。每次版本更新都會修正一些格式細(xì)節(jié)0.37.2 這個版本在 rar 5 的處理上已經(jīng)比較成熟。我一般會建議按這個邊界選型如果業(yè)務(wù)只用標(biāo)準(zhǔn) zip并且需要和 Java 系互傳老老實實用 System.IO.Compression如果出現(xiàn) rar、7z、加密分卷、自定義擴(kuò)展名或者需要流式讀取壓縮包內(nèi)部條目就換 SharpCompress。還有一個容易被忽略的理由官方庫對 zip 的壓縮選項控制很有限SharpCompress 的 Writer 支持按條目指定壓縮類型和壓縮等級這在處理混合內(nèi)容時非常靈活。比如日志文件用快速壓縮圖片文件用存儲模式避免 CPU 空耗。選型時也要注意版本號的策略。SharpCompress 的版本號不是單純的功能遞增0.37.2 屬于較新的穩(wěn)定系列API 與早期 0.30 系列有差異。有些老教程里寫的 ArchiveFactory.WriteTo 在當(dāng)前版本已經(jīng)變了網(wǎng)上搜到的代碼如果用的是舊接口編譯時會直接報錯。這也是我在這里堅持寫 0.37.2 實際可用代碼的原因——版本差異是這個庫最大的學(xué)習(xí)成本之一。2.2 核心對象Reader、Writer 與 Archive 的設(shè)計差異SharpCompress 提供了三套風(fēng)格不同的 API。第一套是只讀的 Reader面向流式讀取壓縮包適合邊讀邊處理內(nèi)存占用低。第二套是 Writer面向?qū)懭肟梢灾饤l添加條目并控制壓縮參數(shù)。第三套是 Archive面向隨機(jī)訪問適合需要查目錄、按條目不連續(xù)讀取的場景。三者各有分工用錯會導(dǎo)致性能問題。例如用 Archive 去讀一個巨大的 tar 包它可能把整個條目目錄讀進(jìn)內(nèi)存而用 Reader 只能順序讀無法跳回上一條。在實際項目中我傾向于把 Reader 當(dāng)作默認(rèn)選擇。對于絕大多數(shù)“從壓縮包里讀出文件流并轉(zhuǎn)發(fā)出去”的場景順序讀就夠了而且 Reader 天然支持從流中讀取不需要知道文件大小。Archive 更適合需要反復(fù)讀取、或者需要讀取分卷壓縮包的時候。Writer 則是寫入時不二之選它和 Reader 共享一套條目抽象所以用 Writer 寫出來的包用 Reader 讀時不會出現(xiàn)奇怪的路徑兼容問題。這里有一個設(shè)計上的坑Reader 和 Archive 的讀取粒度不同。Reader 需要先進(jìn)入某個條目再讀取該條目的流Archive 則可以直接通過 entry.Key / entry.OpenEntryStream() 獲取條目。不要混用。常見錯誤是先用 Archive 枚舉根目錄再試圖用 Reader 讀取同一個壓縮包結(jié)果 Reader 會從頭開始無法定位。如果確實需要隨機(jī)訪問全程用 Archive如果想順序解壓并控制內(nèi)存全程用 Reader。2.3 從 sharpcompress.0.37.2 的包結(jié)構(gòu)看版本約定剛解壓 sharpcompress.0.37.2.zip 時你會看到 lib 目錄下按 netstandard2.0、net462 等不同目標(biāo)框架分了好幾個子目錄。這不是冗余是因為底層 API 在不同 .NET 版本上的實現(xiàn)有差異。選擇對應(yīng)的 DLL 時要看你項目的目標(biāo)框架。如果你用 .NET 6可以直接引用 netstandard2.0 版本兼容性最好。如果誤引用了低版本對應(yīng)的 DLL在運(yùn)行時可能出現(xiàn)方法未找到之類的異常。這個包里還有一個 SharpCompress.Desktop 的區(qū)分。桌面框架下某些格式比如舊版 RAR 的 Unicode 文件名會有額外處理。在 .NET Core 上該庫會退回到純托管實現(xiàn)性能略有下降但功能不變。理解了這一點當(dāng)你遇到“同樣的代碼在 Framework 上正常、在 Core 上拋異常”的問題時就不會去懷疑業(yè)務(wù)邏輯而是優(yōu)先檢查是否踩到了框架差異。版本約定的另一層含義是0.37.2 是發(fā)行包對應(yīng)的源碼標(biāo)簽可以在倉庫的歷史提交里找到。如果你需要修復(fù)某個 bug建議直接拉對應(yīng)版本的源碼構(gòu)建而不是拿主分支的代碼去改。因為主分支可能已經(jīng)引入破壞性變更編譯出來和 NuGet 包行為不一致。我見過有人改了主分支源碼想替換包結(jié)果 API 簽名對不上最后被迫降級。另外建議在項目里鎖定這個包版本因為新版本可能會改變 ReaderOptions 的默認(rèn)值升包后出現(xiàn)行為差異這類問題在上線前很難發(fā)現(xiàn)。3. 把 sharpcompress.0.37.2 跑起來最小解壓與壓縮代碼3.1 用 ZipArchive 完成最小解壓代碼與參數(shù)說明先來一段最常見的解壓代碼用 SharpCompress 讀取 zip 文件里的所有條目并輸出到指定目錄。using SharpCompress.Readers; using (var stream File.OpenRead(C:\data\backup.zip)) using (var reader ReaderFactory.Open(stream)) { while (reader.MoveToNextEntry()) { if (!reader.Entry.IsDirectory) { reader.WriteEntryToDirectory(C:\data\extracted, new ExtractionOptions { ExtractFullPath true, Overwrite true }); } } }這段代碼的邏輯是先用文件流打開壓縮包再用ReaderFactory.Open自動識別格式。MoveToNextEntry在內(nèi)部會判斷當(dāng)前流屬于哪種壓縮類型并將條目指針移動到下一條。遇到目錄條目直接跳過否則寫入目標(biāo)目錄。ExtractionOptions里的ExtractFullPath表示保持壓縮包內(nèi)的相對路徑Overwrite決定是否覆蓋已存在文件。兩個參數(shù)建議顯式設(shè)置因為默認(rèn)值在不同版本里有調(diào)整不寫清楚容易產(chǎn)生“解出來少了一半文件”的錯覺。這里有一個性能注意點WriteEntryToDirectory內(nèi)部會對每個條目打開目標(biāo)文件并復(fù)制流但如果你需要二次處理比如重命名、去重、過濾大文件就不要用這個方法而是自己讀取條目的流??聪旅娴淖凅wusing var reader ReaderFactory.Open(stream); while (reader.MoveToNextEntry()) { if (reader.Entry.IsDirectory) continue; if (reader.Entry.Size 100 * 1024 * 1024) continue; // 跳過 100 MB 以上條目 using var entryStream reader.OpenEntryStream(); using var output File.Create(Path.Combine(targetDir, reader.Entry.Key)); entryStream.CopyTo(output); }由OpenEntryStream返回的流只能在這一個條目內(nèi)讀取移動指針到下一個條目后之前條目對應(yīng)的流就失效。所以循環(huán)內(nèi)不能緩存多個條目的流必須立刻消費(fèi)。Entry.Size在讀取時就可以拿到適合做前置過濾。如果目標(biāo)目錄不存在需要先Directory.CreateDirectory否則File.Create會拋目錄找不到的異常別問我怎么知道的。3.2 用 Writer 生成 tar.gz 壓縮包分步實現(xiàn)解壓之外寫入一個 tar.gz 包是常見的交付需求。SharpCompress 的 Writer 支持單格式寫入但 tar.gz 其實是兩個步驟先生成 tar 流再用 gzip 套一層。常見做法是用WriterFactory.Open傳一個 gzip 壓縮流給 TarWriter。using var fileStream File.Create(C:\data\output.tar.gz); using var gzipStream new GZipStream(fileStream, CompressionLevel.Optimal); using var writer WriterFactory.Open(ArchiveType.Tar, gzipStream); writer.Write(C:\data\file1.txt, file1.txt); writer.Write(C:\data\pic.png, images\pic.png);這里WriterFactory.Open的第一個參數(shù)指定內(nèi)部格式第二個參數(shù)是輸出流。要點是gzip 流在外層tar 流在邏輯上屬于內(nèi)層。Write方法有多個重載最簡單的是傳源文件路徑和條目名。如果需要記錄條目標(biāo)時間、權(quán)限等信息可以傳額外的 FileInfo。注意Write執(zhí)行時并不會立即將內(nèi)容寫入文件流它會在條目數(shù)據(jù)復(fù)制完畢后刷新所以不要提前關(guān)閉gzipStream要等 writer 釋放后再釋放外層流。實際項目中經(jīng)常要壓縮整個目錄手動逐條Write太累??梢员闅v目錄后寫成遞歸函數(shù)但要注意路徑分隔符。統(tǒng)一把條目名里的\替換成/否則在 Linux 上解壓會看到反斜杠文件名。這個細(xì)節(jié)很容易翻車。3.3 內(nèi)存流與文件流的切換避免把大文件讀進(jìn)內(nèi)存很多初學(xué)代碼喜歡寫byte[] data File.ReadAllBytes再塞進(jìn) MemoryStream 交給壓縮庫。這種寫法在小文件沒問題但一旦遇到幾百 MB 的包內(nèi)存立刻告急。SharpCompress 的所有 API 都接受流所以盡量讓文件流貫穿始終。以下寫法是正確的姿勢using var input File.OpenRead(C:\data\big.zip); using var reader ReaderFactory.Open(input); while (reader.MoveToNextEntry()) { // 直接消費(fèi)不經(jīng)過 MemoryStream }如果你必須把解壓結(jié)果放到內(nèi)存里比如后端接口需要把壓縮包內(nèi)容轉(zhuǎn)成 JSON 后再透傳那也要控制單個條目的體積。一個實用的策略是按條目大小做開關(guān)小于 10 MB 讀進(jìn)內(nèi)存大于則寫入臨時文件。這樣既滿足接口的即時處理又不至于把進(jìn)程內(nèi)存撐爆。Entry.Size在流式讀取時是可用的所以這個判斷可以放在OpenEntryStream之前。需要格外注意的是ReaderFactory.Open在讀流時并不預(yù)讀整個壓縮包它只是根據(jù)文件頭判斷格式。所以如果流被包裝過比如加了一層自定義加密直接拋異常。這不算 bug而是設(shè)計如此任何壓縮庫都必須看到標(biāo)準(zhǔn)的文件頭才能工作。如果你的包是加密后再做 base64 傳輸?shù)谋仨毾冉饷苓€原成原始壓縮流。4. 加密、分卷與大文件的流式處理代碼怎么寫才不翻車這一章解決的是最容易讓人放棄的三個場景加密、分卷、流式。這三個需求背后都隱藏著一些不打開源碼就看不出的約定但只要把參數(shù)和調(diào)用姿勢寫對SharpCompress 能處理得相當(dāng)干凈。4.1 Rar 與 7z 的加密解壓密碼參數(shù)怎么傳加密是壓縮領(lǐng)域的大坑。SharpCompress 對 rar 和 7z 的加密支持程度不同。rar 的傳統(tǒng)加密rar2、rar4可以通過ReaderOptions.Password直接解壓而 rar5 的某些加密頭處理在 0.37.2 版本已經(jīng)可用但不是所有變體都支持。7z 則只支持 AES-256 加密的條目如果你遇到“密碼正確但解不開”的情況先確認(rèn)壓縮時選的加密方式。一個正確的加密解壓寫法var options new ReaderOptions { Password your-password, LookForHeader true }; using var reader ReaderFactory.Open(fileStream, options); while (reader.MoveToNextEntry()) { reader.WriteEntryToDirectory(targetDir, new ExtractionOptions { ExtractFullPath true, Overwrite true }); }這里的LookForHeader會讓庫在流中搜索文件頭而不是假設(shè)流起始就是文件頭。這個參數(shù)對于從大文件中提取內(nèi)嵌壓縮包很有用但也會增加掃描耗時。在確定壓縮包就是完整文件的場景下建議設(shè)為false以提升性能。密碼錯誤時MoveToNextEntry或讀取條目流時會拋出密碼錯誤異常不要在主循環(huán)里只 catch 一次就以為全部處理完畢要區(qū)分條目級別。注意同一個加密包在 Windows 和 Linux 上對密碼的處理可能不一致和系統(tǒng)區(qū)域的 UTF-8 設(shè)置有關(guān)。遇到詭異問題時先嘗試把密碼用 ASCII 重新輸入。另一個細(xì)節(jié)不要把密碼寫死在代碼里。日志、配置文件都可能被掃描到。我一般把密碼放環(huán)境變量配合IConfiguration注入。壓縮包來源不可信時更要注意不要用同一套密碼解壓所有文件否則相當(dāng)于把鑰匙交給了不可控的代碼路徑。4.2 分卷壓縮 Volume 的處理連續(xù)文件的聚合邏輯rar 分卷.part1.rar、.part2.rar和 7z 分卷.7z.001、.7z.002是另一個高頻需求。SharpCompress 的 Reader 本身不支持直接“吃”多個分卷流需要你自己把多個文件流串起來。常見做法是打開第一個分卷其余分卷按順序作為追加流傳入。一個實用的分卷解壓實現(xiàn)var files Directory.GetFiles(C:\data\, *.part*.rar) .OrderBy(f f, StringComparer.OrdinalIgnoreCase) .ToArray(); using var primary File.OpenRead(files[0]); using var reader ReaderFactory.Open(primary, new ReaderOptions { Password password }); while (reader.MoveToNextEntry()) { reader.WriteEntryToDirectory(C:\data\out, new ExtractionOptions { ExtractFullPath true }); }但這其實只讀到了第一個分卷后面的分卷不會被自動識別。SharpCompress 提供了 Volume 相關(guān)對象但不同格式的處理方式不一致。對于 rar你需要手動處理跨卷條目的拼接。最簡單的方案是先解壓第一個分卷當(dāng)某個條目的流讀完但校驗失敗時關(guān)閉當(dāng)前流并打開下一個分卷再繼續(xù)。實際操作時我建議直接調(diào)用ArchiveFactory.Open打開第一個文件它會自動尋找同目錄下的分卷文件因為 Archive 模式實現(xiàn)了分卷發(fā)現(xiàn)邏輯。所以分卷場景請用 Archive 而不是 Reader。代碼大致是using var archive ArchiveFactory.Open(files[0]); foreach (var entry in archive.Entries) { entry.WriteToDirectory(C:\data\out, new ExtractionOptions { ExtractFullPath true, Overwrite true }); }注意分卷文件的命名必須連續(xù)且在同一目錄否則自動發(fā)現(xiàn)會失敗。分卷文件缺失時庫拋出的異常信息往往只寫“CRC 錯誤”很容易讓人誤以為文件損壞。實際原因是后續(xù)分卷沒有找到。遇到這種異常時先檢查目錄里分卷是否齊全再檢查是不是命名大小寫不匹配。如果文件列表為空直接拋異常提示用戶要比后續(xù)解壓過程中的任何錯誤都容易定位。4.3 流式讀取條目解壓不落盤直接進(jìn)管道把解壓數(shù)據(jù)直接傳給下一個處理模塊可以省去中間文件讀寫。這在處理 zip 內(nèi)含多個文件、需要逐一轉(zhuǎn)碼的場景非常有用。using var reader ReaderFactory.Open(input, new ReaderOptions { LeaveOpen true }); while (reader.MoveToNextEntry()) { using var s reader.OpenEntryStream(); // 直接寫入 HTTP 響應(yīng)流而不是寫到磁盤 s.CopyTo(httpResponse.Body); }LeaveOpen控制關(guān)閉 reader 時是否同時關(guān)閉底層流。如果底層流是網(wǎng)絡(luò)流就要設(shè)置LeaveOpen true否則 reader 釋放時會把網(wǎng)絡(luò)連接一起關(guān)掉。如果底層流是文件流設(shè)false會更省心。流式讀取的另一個好處是可以用CopyToAsync實現(xiàn)異步處理后面章節(jié)會講。有一個容易忽略的參數(shù)ReaderOptions.LeaveOpen在不同版本中默認(rèn)值不同。在 0.37.2 里默認(rèn)是 false。如果你是從舊版本升級上來的代碼之前沒顯式設(shè)置也能工作升級后可能突然報“流已關(guān)閉”這就是版本行為變化導(dǎo)致的。所以升級包之后務(wù)必檢查所有ReaderOptions的默認(rèn)值變更。對于超過 2GB 的壓縮包還要考慮文件流的緩存策略。打開底層 FileStream 時用FileOptions.SequentialScan可以降低文件系統(tǒng)預(yù)讀的沖擊反之如果你要隨機(jī)讀取多個條目用FileOptions.RandomAccess更合適。這也是我在處理超大包時常用的一個調(diào)優(yōu)點。5. SharpCompress 避坑指南五個現(xiàn)象背后的真實原因SharpCompress 整體設(shè)計不算復(fù)雜但真正遇到問題時網(wǎng)上資料很少大家基本靠試。我把這一年多在 0.37.2 上踩過的坑按頻率排了序下面五條最值得留意。每條都按“現(xiàn)象 → 原因 → 解決”的順序?qū)懩憧梢灾苯訉φ兆约旱拇a排查。5.1 條目名亂碼不是庫的問題是壓縮包創(chuàng)建端的問題現(xiàn)象解壓 rar 或舊 zip文件名顯示成“錕斤拷”或省略號或者在 Linux 上解壓后路徑全變成下劃線。原因SharpCompress 默認(rèn)按 UTF-8 解析而老工具用 GBK/CP936 寫入。0.37.2 沒有暴露解碼器注入點所以你不能直接在 ReaderOptions 里指定編碼。解決讀取 entry.Key 后如果包含“?”或明顯亂碼把它按 ISO-8859-1 轉(zhuǎn)回 byte[]再按 GBK 重新編碼。示例代碼var rawBytes Encoding.Latin1.GetBytes(entry.Key); var corrected Encoding.GetEncoding(GBK).GetString(rawBytes);然后使用 corrected 作為目標(biāo)文件名。注意不要先寫成字符串再轉(zhuǎn)因為 .NET 的字符串已經(jīng)破壞了原始字節(jié)序列。這個坑很隱蔽但用一次就能記住。5.2 OutOfMemoryException多半是 Archive 模式惹的禍現(xiàn)象解壓幾個 GB 的 tar 包進(jìn)程內(nèi)存飆到 1.5GB 并拋 OutOfMemoryException。原因ArchiveFactory.Open 會構(gòu)造整個文件列表的樹形結(jié)構(gòu)。tar 包如果包含幾十萬個文件這些對象占用的內(nèi)存遠(yuǎn)大于壓縮包本身。解決改用 ReaderFactory.Open 順序讀取因為 Reader 不會預(yù)加載條目目錄。如果確實需要 Archive 的隨機(jī)訪問嘗試分批處理先用 Reader 把索引落盤再按需定位。另外檢查是不是在 MoveToNextEntry 循環(huán)里用 MemoryStream 暫存了太多條目要確保每個條目處理完就釋放。5.3 同代碼不同運(yùn)行時的差異先查目標(biāo)框架再查異常現(xiàn)象同一段解壓代碼在 .NET Framework 4.7.2 上正常遷移到 .NET 6 后拋 NotSupportedException。原因SharpCompress 內(nèi)部使用了一些桌面框架獨有的 API 處理舊格式這些分支在 netstandard2.0 目標(biāo)里被排除。0.37.2 的發(fā)行包里有多個 DLL項目引用錯了也會出現(xiàn)同樣現(xiàn)象。解決檢查項目生成的 deps.json 或程序集加載日志確認(rèn)加載的是 lib/netstandard2.0/SharpCompress.dll。如果還有問題不要試圖在 ReaderOptions 里找不存在的“編碼”或“兼容模式”屬性而是去查異常堆棧里的 “PlatformNotSupported” 標(biāo)志對應(yīng)到該版本源碼的條件編譯分支手動繞過。5.4 遍歷慢得像死循環(huán)關(guān)掉 LookForHeader 試試現(xiàn)象一個只有 200 個條目的 zip 包MoveToNextEntry 循環(huán)卻卡了 10 秒。原因ReaderOptions 默認(rèn)的 LookForHeader 為 true它會從流的頭部或當(dāng)前位置向后掃描文件頭。這個掃描在完整文件流上完全多余還會把未壓縮的字節(jié)也拖進(jìn)檢測流程。解決在確定流起始就是壓縮包文件頭時顯式設(shè)置LookForHeader false。同時使用 FileStream 時不要用異步讀取模式這會引入額外緩沖。改完通常能把冷啟動時間降一個數(shù)量級。如果包里條目數(shù)很大還可以考慮在解析前用文件流預(yù)讀一部分字節(jié)識別真實格式避免讓庫在未知位置反復(fù)試探。5.5 寫入壓縮包時進(jìn)度事件沒有觸發(fā)現(xiàn)象調(diào)用 writer.Write 時傳入的 IProgress 一次都沒回調(diào)。原因Write 方法的重載有很多只有帶 progress 參數(shù)的那個重載才會報告字節(jié)進(jìn)度。如果誤用了不帶 progress 的重載當(dāng)然沒有回調(diào)。解決使用writer.Write(entryPath, entryName, new FileInfo(entryPath), progress)重載。注意進(jìn)度是按字節(jié)計不是按條目計所以要先知道你寫入的總大小。想要百分比就用一個累加器除以總大小。如果你在解壓端看到進(jìn)度條亂跳多半是多個條目的進(jìn)度疊加了需要每個條目單獨建一個 progress 實例。6. 進(jìn)階技巧并行解壓與性能驗證的最后一公里當(dāng)單個壓縮包內(nèi)文件很多但彼此獨立很多人會想到多線程提升速度。但 SharpCompress 的 Reader 和 Archive 都不是線程安全的同一個流不能并發(fā)讀多個條目。正確做法是按壓縮包粒度并行而不是按條目并行。var zipFiles Directory.GetFiles(C:\data\, *.zip); await Parallel.ForEachAsync(zipFiles, new ParallelOptions { MaxDegreeOfParallelism 4 }, async (file, ct) { await Task.Run(() { using var reader ReaderFactory.Open(File.OpenRead(file)); while (reader.MoveToNextEntry()) { reader.WriteEntryToDirectory(C:\data\out, new ExtractionOptions { ExtractFullPath true, Overwrite false }); } }, ct); });MaxDegreeOfParallelism要按磁盤 IO 能力和目標(biāo)運(yùn)行環(huán)境調(diào)整。壓滿 CPU 并不等于壓滿磁盤通常 4 到 8 對機(jī)械盤已經(jīng)很高了。我一直習(xí)慣在跑完一批后對比總耗時與資源占用而不是盲目調(diào)大并發(fā)數(shù)。驗證方式很簡單用Stopwatch計時同時用Process.WorkingSet64觀察內(nèi)存。如果并發(fā)數(shù)翻倍但耗時沒降說明瓶頸在磁盤或解壓算法的單線程部分。我踩過一個很慘的坑曾經(jīng)為了提高解壓吞吐讓多個線程直接操作同一個ReaderFactory.Open出來的流結(jié)果出現(xiàn)大量 CRC 錯誤。排查了一個下午最后才確認(rèn)流內(nèi)部有共享狀態(tài)根本不適合并發(fā)讀。那之后我給自己定了一條鐵律凡是MoveToNextEntry循環(huán)體里的操作永遠(yuǎn)不允許并行要并行就并行整個解壓流程。另外建議在發(fā)布前做一次小規(guī)模的性能基線測試固定同樣的輸入記錄 CPU、內(nèi)存和耗時。SharpCompress 的版本升級可能會改變默認(rèn)壓縮策略0.37.2 相比早期版本在 rar5 解壓上優(yōu)化明顯但 zip 寫入的壓縮等級默認(rèn)值有變動。不要相信直覺跑一次數(shù)據(jù)再決定要不要加WriterOptions參數(shù)。希望幫到你。本文還有配套的精品資源點擊獲取