JAVA Zip导入导出实现
一、最简版代码结构
1. 导出核心代码(Controller层)
@GetMapping("/export")
public void exportZip(HttpServletResponse response) throws IOException {// 1. 设置响应头response.setContentType("application/zip");response.setHeader("Content-Disposition", "attachment; filename=export.zip");// 2. 获取输出流try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {// 3. 向zip中添加文件zipOut.putNextEntry(new ZipEntry("test.txt"));zipOut.write("Hello Zip!".getBytes());zipOut.closeEntry();}
}
2. 导入核心代码(Controller层)
@PostMapping("/import")
public String importZip(@RequestParam MultipartFile file) throws IOException {try (ZipInputStream zipIn = new ZipInputStream(file.getInputStream())) {ZipEntry entry;while ((entry = zipIn.getNextEntry()) != null) {// 处理每个文件if (!entry.isDirectory()) {byte[] content = zipIn.readAllBytes();System.out.println("文件: " + entry.getName() + ", 大小: " + content.length);}}}return "导入成功";
}
二、三大核心要点详解
要点1:HttpServletResponse 输出流处理
关键点:
- 必须设置正确的响应头:
response.setContentType("application/zip"); // MIME类型 response.setHeader("Content-Disposition", "attachment; filename=xxx.zip"); // 下载文件名
- 直接使用response的输出流,不要用其他方式返回
- 流会在请求结束时自动关闭,无需手动关闭
常见坑:
- 忘记设置Content-Type会导致浏览器无法识别
- 在Controller中返回其他对象会导致Zip数据被破坏
要点2:Zip文件结构操作
关键API:
// 创建ZIP条目(文件/目录)
zipOut.putNextEntry(new ZipEntry("文件夹/子文件.txt"));// 写入内容(文本/二进制均可)
zipOut.write(byte[] data);// 必须关闭当前条目
zipOut.closeEntry();
目录结构技巧:
- 使用
/
分隔路径,如"data/docs/1.txt"
- 空文件夹需要创建单独的目录条目
- 建议保持UTF-8编码避免中文乱码:
new ZipOutputStream(out).setEncoding("UTF-8");
要点3:大文件处理优化
内存优化方案:
// 使用缓冲流(默认8KB缓冲区)
try (ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()))) {// ...
}// 分块读取大文件
byte[] buffer = new byte[8192]; // 8KB缓冲区
int len;
while ((len = inputStream.read(buffer)) > 0) {zipOut.write(buffer, 0, len);
}
性能对比:
方式 | 内存占用 | 适用场景 |
---|---|---|
readAllBytes() | 高 | 小文件(<1MB) |
分块读写 | 低 | 大文件(>1MB) |
三、实际应用场景示例
场景1:导出用户数据报表
public void exportUserReports(List<User> users, ZipOutputStream zipOut) throws IOException {// 1. 添加CSV汇总文件zipOut.putNextEntry(new ZipEntry("summary.csv"));zipOut.write("ID,姓名,年龄\n".getBytes());for (User user : users) {String line = String.format("%d,%s,%d\n", user.getId(), user.getName(), user.getAge());zipOut.write(line.getBytes());}zipOut.closeEntry();// 2. 为每个用户创建PDFfor (User user : users) {String entryName = "users/" + user.getId() + ".pdf";zipOut.putNextEntry(new ZipEntry(entryName));byte[] pdf = generatePdf(user);zipOut.write(pdf);zipOut.closeEntry();}
}
场景2:导入图片压缩包
public List<String> importImages(ZipInputStream zipIn) throws IOException {List<String> savedPaths = new ArrayList<>();ZipEntry entry;while ((entry = zipIn.getNextEntry()) != null) {if (entry.getName().endsWith(".jpg") || entry.getName().endsWith(".png")) {String savePath = "/uploads/" + UUID.randomUUID() + ".jpg";try (OutputStream out = new FileOutputStream(savePath)) {byte[] buffer = new byte[8192];int len;while ((len = zipIn.read(buffer)) > 0) {out.write(buffer, 0, len);}}savedPaths.add(savePath);}}return savedPaths;
}
四、常见问题解决方案
问题1:中文文件名乱码
解决方案:
// 创建ZipOutputStream时指定编码
ZipOutputStream zipOut = new ZipOutputStream(out);
zipOut.setEncoding("UTF-8");// 或者使用Apache Commons Compress
ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(out);
zipOut.setEncoding("UTF-8");
问题2:ZIP炸弹防护
安全检测方案:
// 检查压缩比异常
if (entry.getCompressedSize() > 0 && entry.getSize() / entry.getCompressedSize() > 50) {throw new SecurityException("可疑的压缩比");
}// 检查总文件数
int maxEntries = 10000;
int entryCount = 0;
while ((entry = zipIn.getNextEntry()) != null) {if (++entryCount > maxEntries) {throw new SecurityException("文件数量超过限制");}// ...处理逻辑
}
问题3:大内存占用
流式处理方案:
public void exportLargeFile(ZipOutputStream zipOut, String filePath) throws IOException {zipOut.putNextEntry(new ZipEntry("large_file.data"));try (InputStream in = new BufferedInputStream(new FileInputStream(filePath))) {byte[] buffer = new byte[8192];int len;while ((len = in.read(buffer)) > 0) {zipOut.write(buffer, 0, len);}}zipOut.closeEntry();
}
五、最佳实践建议
-
目录结构规范:
- 使用统一的前缀如
export_2023/data/
- 避免使用绝对路径如
C:/temp/
- 文件名使用字母数字和下划线
- 使用统一的前缀如
-
日志记录:
logger.info("开始导出ZIP,包含{}个文件", fileCount); logger.debug("正在处理文件: {}", entry.getName());
-
单元测试要点:
- 测试空zip文件处理
- 测试包含特殊字符的文件名
- 测试超过1GB的大文件
-
前端配合建议:
// AJAX下载示例 axios.get('/export', {responseType: 'blob' }).then(response => {const url = window.URL.createObjectURL(new Blob([response.data]));const link = document.createElement('a');link.href = url;link.setAttribute('download', 'export.zip');document.body.appendChild(link);link.click(); });
记住:Zip处理的核心就是流操作,掌握好ZipOutputStream
和ZipInputStream
的使用,就能应对大多数导入导出需求。