Java壓縮和解壓縮ZIP文件實戰(zhàn)案例
前言
在現(xiàn)代計算機上,數(shù)據(jù)傳輸和存儲越來越依賴于文件壓縮技術(shù)。當我們需要發(fā)送大量數(shù)據(jù)時,壓縮文件可以大大減少傳輸時間和網(wǎng)絡(luò)帶寬,而且壓縮文件還可以幫助我們節(jié)省磁盤空間。在Java中提供了壓縮和解壓縮文件的功能,可以使用java.util.zip包中的類來實現(xiàn)。本篇將對如何使用 Java 實現(xiàn)單文多件壓縮和解壓縮進行總結(jié)。
文件壓縮指的是將一個或多個文件通過壓縮算法,將其存儲為一個更小的文件,以便于存儲和傳輸。壓縮的原理是通過對文件的數(shù)據(jù)進行編碼和壓縮,使其占用更少的空間。壓縮后的文件可以通過解壓縮算法還原成原始的文件格式。在文件壓縮過程中,常見的操作是將多個文件打包成一個壓縮文件,例如zip、tar等格式。
Java解壓縮文件
常見的文件壓縮格式包括:
- ZIP:最常見的壓縮文件格式之一,可以存儲一個或多個文件,并可在不同的操作系統(tǒng)中進行解壓縮。
- TAR:Linux系統(tǒng)中的常見文件壓縮格式,通常用于打包多個文件,但不會進行壓縮。
- GZIP:常用的文件壓縮格式,通常用于壓縮單個文件,可以獲得更高的壓縮比。
- BZIP2:高效的壓縮算法,通常用于壓縮文本文件和XML文件等。
- JAR: Jar包對于Java開發(fā)同學(xué)來說肯定很熟悉,其也是一個壓縮包
Java提供了多種用于壓縮和解壓縮文件的API,主要包括以下類和方法:
- ZipOutputStream 和 ZipInputStream:用于創(chuàng)建和讀取ZIP格式的壓縮文件。
- GZIPOutputStream 和 GZIPInputStream:用于創(chuàng)建和讀取GZIP格式的壓縮文件。
- JarOutputStream 和 JarInputStream:用于創(chuàng)建和讀取JAR格式的壓縮文件。
- DeflaterOutputStream 和 InflaterInputStream:用于創(chuàng)建和讀取DEFLATE格式的壓縮文件。
- CheckedOutputStream 和 CheckedInputStream:用于在壓縮和解壓縮過程中計算文件的校驗和。
壓縮和解壓縮ZIP文件
通過使用Java 自帶的 java.util.zip
類庫下的ZipOutputStream
、ZipInputStream
、ZipEntry
實現(xiàn)文件的壓縮和解壓縮,其中ZipOutputStream
用于創(chuàng)建ZIP壓縮文件輸出流輸出ZIP壓縮文件,ZipInputStream
用于創(chuàng)建ZIP文件輸入流讀取ZIP文件用于解壓縮而ZipEntry
對應(yīng)ZIP壓縮包中的每個被壓縮對象;
生成ZIP文件
壓縮單個文件或者單個文件夾方法,代碼如下:
/** * 壓縮文件(支持單個文件和單個文件夾) * @param sourceFile 被壓縮文件/文件夾 * @param zipFile Zip文件 */ public static void zipCompress(File sourceFile, File zipFile) { try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { // 設(shè)置壓縮方法 zos.setMethod(ZipOutputStream.DEFLATED); zos.setLevel(Deflater.BEST_COMPRESSION); // 默認為-1,壓縮級別,1速度快,效率低,9 速度滿,效率高 // zos.setLevel(Deflater.BEST_SPEED); zos.setComment("zip文件說明"); // 處理文件夾 if (sourceFile.exists() && sourceFile.isDirectory() && Objects.nonNull(sourceFile.listFiles())){ Arrays.stream(Objects.requireNonNull(sourceFile.listFiles())).forEach(file -> { addZipFile(file, zos); }); }else{ addZipFile(sourceFile, zos); } } catch (IOException e) { e.printStackTrace(); } }
為了支持讀取單個文件夾進行壓縮,增加一個向ZipOutputStream
中添加ZipEntry
的方法,代碼如下:
/** * 向ZIP中添加文件 * @param file 源文件 * @param zos zip輸出流 */ private static void addZipFile(File file, ZipOutputStream zos){ if (!file.exists() || file.isDirectory()){ throw new RuntimeException("文件不存在或該文件為文件夾,請檢查"); } try { // 讀入文件 FileInputStream fis = new FileInputStream(file); // 創(chuàng)建壓縮對象并設(shè)置一些屬性 ZipEntry entry = new ZipEntry(file.getName()); entry.setMethod(ZipEntry.DEFLATED); // 壓縮方法默認為DEFLATED // entry.setMethod(ZipEntry.STORED); // STORED(不壓縮)。當使用STORED壓縮方法時,需要設(shè)置未壓縮的數(shù)據(jù)大小和CRC-32校驗和,否則壓縮和解壓縮時會出現(xiàn)錯誤。 entry.setSize(file.length()); // 設(shè)置未壓縮的數(shù)據(jù)大小,這里設(shè)置的是文件大小 // 計算 CRC-32 校驗碼 // byte[] data = Files.readAllBytes(file.toPath()); // CRC32 crc = new CRC32(); // crc.update(data); // entry.setCrc(crc.getValue()); // 設(shè)置CRC-32校驗和,用于保證壓縮后的數(shù)據(jù)完整性,盡量別手動設(shè)置,可以通過CRC-32計算 entry.setCompressedSize(file.length()); // 設(shè)置壓縮后的數(shù)據(jù)大小,這里設(shè)置的是使用DEFLATED方法壓縮后的數(shù)據(jù)大小 entry.setExtra(new byte[]{}); // 設(shè)置額外的數(shù)據(jù),這里設(shè)置為空 entry.setComment("file comment"); // 設(shè)置ZipEntry的注釋,即文件說明 entry.setCreationTime(FileTime.from(Instant.now())); // 設(shè)置文件的創(chuàng)建時間 entry.setLastAccessTime(FileTime.from(Instant.now())); // 設(shè)置文件的最后訪問時間 entry.setLastModifiedTime(FileTime.from(Instant.now())); // 設(shè)置文件的最后修改時間。 // 向ZIP輸出流中添加一個ZIP實體,構(gòu)造方法中的name參數(shù)指定文件在ZIP包中的文件名 zos.putNextEntry(entry); // 向ZIP實體中寫入內(nèi)容 byte[] buf = new byte[1024]; int len; while ((len = fis.read(buf)) > 0) { zos.write(buf, 0, len); } // 關(guān)閉ZipEntry zos.closeEntry(); } catch (IOException e) { e.printStackTrace(); } }
注意:
涉及到文件IO流,如果沒有使用try with source 的語法,一定要記得關(guān)閉輸入輸出流;
使用Java.util.zip下的工具類壓縮成ZIP不支持設(shè)置ZIP密碼且每種模式下生成的ZIP文件大小大于等于原文件/文件夾;
當使用STORED壓縮方法時,需要設(shè)置未壓縮的數(shù)據(jù)大小和CRC-32校驗和,該值需要借助CRC-32計算非常的麻煩,不設(shè)置此值會拋出異常;
對于壓縮,可以使用 ZipOutputStream 的 putNextEntry 方法逐個添加文件,避免將所有文件一次性讀入內(nèi)存;
可以設(shè)置緩沖區(qū)大小,例如對于文件的讀取和寫入,可以設(shè)置緩沖區(qū)大小為 4KB 或者 8KB,減少內(nèi)存的占用;
對于解壓縮,可以使用 ZipInputStream 的 getNextEntry 方法逐個讀取文件,避免將所有文件一次性讀入內(nèi)存;
解壓縮ZIP文件
將ZIP文件解壓縮支持生成文件夾,代碼如下:
/** * 解壓縮ZIP文件 * @param zipFile ZIP文件 * @param destDir 目標路徑 */ public static void zipDecompress(File zipFile, File destDir) { byte[] buffer = new byte[1024]; try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) { ZipEntry entry = zis.getNextEntry(); while (entry != null) { File file = new File(destDir, entry.getName()); if (entry.isDirectory()) { file.mkdirs(); } else { File parent = file.getParentFile(); if (!parent.exists()) { parent.mkdirs(); } try (FileOutputStream fos = new FileOutputStream(file)) { int len; while ((len = zis.read(buffer)) > 0) { fos.write(buffer, 0, len); } } } entry = zis.getNextEntry(); } } catch (IOException e) { e.printStackTrace(); } }
如何避免壓縮文件中的注入攻擊?
壓縮文件中的路徑名和文件名可以被精心構(gòu)造的攻擊者利用,從而使得解壓縮的過程中可能會導(dǎo)致路徑遍歷、文件覆蓋等問題,進而導(dǎo)致安全問題。為了避免這些安全問題,可以進行如下處理:
- 限制壓縮文件中的路徑名和文件名的長度,以及字符集,可以采用白名單機制對輸入進行限制;
- 對于解壓縮的路徑名和文件名,不要使用壓縮文件中的路徑名和文件名,而是在解壓縮時自行構(gòu)造一個路徑名和文件名;
- 對于不可信任的壓縮文件,最好在安全的環(huán)境下解壓縮,例如在沙箱或虛擬機中進行操作。
檢驗
主要測試使用上述代碼壓縮單文件和解壓縮單文件能否成功以后測量單個文件的處理耗時,同時測試單文件夾多文件壓縮和解壓縮能否成功以及處理耗時,單元測試代碼如下:
@Test void testJavaUtilZip(){ // 測試壓縮和解壓縮單文件ZIP // 被壓縮的MP4單文件,大小112.5MB File inputFile = new File("/Users/zlc/Documents/own/images/GPT-4 Developer Livestream.mp4"); // ZIP文件路徑 File zipFile = new File("/Users/zlc/Documents/own/mp4.zip"); // ZIP 解壓縮路徑 File unzipFile = new File("/Users/zlc/Documents/own/unzip"); long start = System.currentTimeMillis(); // 壓縮文件 ZipFileUtil.zipCompress(inputFile, zipFile); long end = System.currentTimeMillis(); System.out.println("ZIP-壓縮單文件耗時:" + (end - start) + "毫秒"); start = System.currentTimeMillis(); ZipFileUtil.zipDecompress(zipFile, unzipFile); end = System.currentTimeMillis(); System.out.println("ZIP-解壓縮單文件耗時:" + (end - start) + "毫秒"); // 單文件夾多文件壓縮和解壓縮測試 // 文件夾大小2.42G File inputFiles = new File("/Users/zlc/Documents/own/images"); File zipFiles = new File("/Users/zlc/Documents/own/imagesZip.zip"); File unzipFiles = new File("/Users/zlc/Documents/own/imagesUnzip"); start = System.currentTimeMillis(); // 壓縮文件 ZipFileUtil.zipCompress(inputFiles, zipFiles); end = System.currentTimeMillis(); System.out.println("ZIP - 多文件壓縮耗時:" + (end - start) + "毫秒"); start = System.currentTimeMillis(); ZipFileUtil.zipDecompress(zipFiles, unzipFiles); end = System.currentTimeMillis(); System.out.println("ZIP - 多文件解壓縮耗時:" + (end - start) + "毫秒"); }
測試結(jié)果如下:
ZIP-壓縮單文件耗時:5492毫秒
ZIP-解壓縮單文件耗時:1920毫秒
ZIP - 多文件壓縮耗時:136059毫秒
ZIP - 多文件解壓縮耗時:45739毫秒
同時測試了設(shè)置不同壓縮等級的耗時比較,結(jié)果如下:
// 不同壓縮等級下的處理耗時 BEST_COMPRESSION 2.43G 文件夾耗時119801毫秒快兩分鐘了 BEST_SPEED 2.43G 文件夾耗時112646毫秒 也沒差多少,但確實快了
結(jié)論:
測試環(huán)境為MacOS 四核Intel Core i7,16G內(nèi)存,處理將近2.5G大小的文件夾耗時接近兩分鐘,效率十分低下,在總文件大小不大的時候可以考慮使用JDK自帶的壓縮工具類。
應(yīng)用
設(shè)計一個API,通過使用Hutool生成兩個CSV文件和一個Excel文件,將這三個文件放入到ZIP壓縮包中,當通過瀏覽器調(diào)用API時,下載ZIP壓縮包。
同時在服務(wù)器上不生成CVS、EXCEL以及Zip文件而是直接通過HttpServletResponse將文件傳送給客戶端,避免服務(wù)器因過多生成這些文件導(dǎo)致服務(wù)硬盤不夠用(PS:生成文件以后基本上不會有人管了,會隨著時間的增加爆炸式增加,當然如果你需要留存建議生成本地文件存儲到OSS中),代碼如下:
/** * 下載ZIP * @param response HttpServletResponse 響應(yīng)流 * @return zip file */ @GetMapping(value = "/downloadZip") public String downloadZipFile(HttpServletResponse response) { // 設(shè)置響應(yīng)頭 response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=download.zip"); try (ZipOutputStream outputStream = new ZipOutputStream(response.getOutputStream())) { ExcelWriter writer = new ExcelWriter(true); List<String> header = Arrays.asList("開始日期", "結(jié)束日期", "算法廠商", "期末資產(chǎn)總額", "累計成交金額", "期間委托筆數(shù)", "期間成交筆數(shù)", "期間撤單筆數(shù)"); writer.writeHeadRow(header); List<List<String>> rows = new ArrayList<>(); rows.add(Arrays.asList("張三", "里斯", "男", "張三", "里斯", "男", "張三", "里斯")); rows.add(Arrays.asList("李四", "王武", "女", "張三", "里斯", "男", "張三", "里斯")); writer.write(rows); writer.passRows(1); List<String> header1 = Arrays.asList("日期", "資金賬號", "算法母單編號", "委托編號", "交易所", "股票代碼", "委托數(shù)量", "交易方向", "訂單類型", "委托價格", "委托狀態(tài)", "累計成交數(shù)量", "累計成交金額"); writer.writeHeadRow(header1); writer.autoSizeColumn(writer.getCurrentRow()); List<List<String>> rows2 = new ArrayList<>(); rows2.add(Arrays.asList("張三", "里斯", "男", "張三", "里斯", "男", "張三", "里斯", "找大大", "趙打打", "炸", "茅臺", "米線")); rows2.add(Arrays.asList("李四", "王武", "女", "張三", "里斯", "男", "張三", "里斯", "找大大", "趙打打", "炸", "茅臺", "米線")); writer.write(rows2); writer.autoSizeColumnAll(); ZipEntry entry = new ZipEntry("數(shù)據(jù).xlsx"); outputStream.putNextEntry(entry); writer.flush(outputStream); writer.close(); outputStream.closeEntry(); ZipEntry entry1 = new ZipEntry("母單.csv"); outputStream.putNextEntry(entry1); CsvWriter csvWriter1 = new CsvWriter(new OutputStreamWriter(outputStream)); String[] csvHead1 = {"日期", "資金賬號", "算法廠商", "算法", "算法母單編號", "交易所", "股票代碼", "委托數(shù)量", "交易方向", "啟動時間", "停止時間", "算法狀態(tài)"}; csvWriter1.writeLine(csvHead1); String[] csvData1 = {"20230203", "98830901", "XX", "TWAP", "12984", "SH", "600000", "10000", "4", "102311", "112311", "1"}; String[] csvData2 = {"20230203", "98830901", "XX", "TWAP", "12984", "SH", "600000", "10000", "4", "102311", "112311", "1"}; csvWriter1.writeLine(csvData1); csvWriter1.writeLine(csvData2); csvWriter1.flush(); outputStream.closeEntry(); ZipEntry entry2 = new ZipEntry("子單.csv"); outputStream.putNextEntry(entry2); CsvWriter csvWriter2 = new CsvWriter(new OutputStreamWriter(outputStream)); String[] csvHead2 = {"日期", "資金賬號", "算法母單編號", "委托編號", "交易所", "股票代碼", "委托數(shù)量", "交易方向", "訂單類型", "委托價格", "委托狀態(tài)", "累計成交數(shù)量", "累計成交金額"}; csvWriter2.writeLine(csvHead2); String[] csvData3 = {"20230203", "98830901", "12984", "ord1122", "SH", "600000", "1000", "4", "1", "7.23", "1", "800", "5600"}; String[] csvData4 = {"20230203", "98830901", "12984", "ord1122", "SH", "600000", "1000", "4", "1", "7.23", "1", "800", "5600"}; csvWriter2.writeLine(csvData3); csvWriter2.writeLine(csvData4); csvWriter2.flush(); outputStream.closeEntry(); outputStream.finish(); return "success"; } catch (Exception e) { return "faild"; } }
總結(jié)
Java 自帶的 java.util.zip
類庫是一個基礎(chǔ)的壓縮和解壓縮類庫,它提供了很基本的壓縮和解壓縮功能。在處理小型文件或數(shù)據(jù)時,java.util.zip
是一個可行的選擇。不過,對于大型文件或數(shù)據(jù)的處理,效率可能會受到影響。相比之下,一些第三方的類庫如 Apache Commons Compress、Zip4j 等提供了更為高級的壓縮和解壓縮功能,同時也提供了更好的性能。
到此這篇關(guān)于Java壓縮和解壓縮ZIP文件的文章就介紹到這了,更多相關(guān)Java壓縮和解壓縮ZIP文件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IntelliJ Idea 2020.1 正式發(fā)布,官方支持中文(必看)
這篇文章主要介紹了IntelliJ Idea 2020.1 正式發(fā)布,官方支持中文了,本文通過截圖的形式給大家展示,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04springcloud項目快速開始起始模板的實現(xiàn)
本文主要介紹了springcloud項目快速開始起始模板思路的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12深入了解SpringAOP中的jdk動態(tài)代理與CGlib
這篇文章主要介紹了深入了解SpringAOP中的jdk動態(tài)代理與CGlib,一般我們編寫程序的思想是縱向的,也就是一個方法代碼從該方法第一行開始往下一步一步走,直到走完最后一行代碼,也就是說很多業(yè)務(wù)都需要的比如用戶鑒權(quán),資源釋放等,需要的朋友可以參考下2023-12-12