Azure Blob多线程压缩文件报错:no current ZIP entry
问题:Azure Blob多线程压缩文件抛出ZipException异常
我在Azure Blob中压缩文件时,单线程常规压缩能正常生成压缩包,但改用CompletableFuture多线程处理时抛出异常。
代码示例
try (ZipOutputStream zipStream = new ZipOutputStream(outputStream)) { List<CompletableFuture<Void>> futures = new ArrayList<>(); for (String singleFileName : fileNames) { String singleFilePath = azureFilePath + "/" + singleFileName.trim(); BlobClient blobClient = blobContainerClient.getBlobClient(singleFilePath); CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { try (ByteArrayOutputStream fileOutputStream = new ByteArrayOutputStream()) { if (blobClient.exists()) { // Download the file to a separate output stream blobClient.download(fileOutputStream); // Add the file to the zip stream zipStream.putNextEntry(new ZipEntry(singleFileName)); zipStream.write(fileOutputStream.toByteArray()); // zipStream.closeEntry(); } else { throw new RuntimeException("File not found: " + singleFilePath); } } catch (IOException e) { throw new RuntimeException("Error processing file: " + singleFileName, e); }finally { try { zipStream.closeEntry(); } catch (IOException e) { // Handle any potential exception when closing the zip entry e.printStackTrace(); } } }); futures.add(future); } CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); allOf.join(); } // Upload the zip file to the same location BlobClient zipFileClient = blobContainerClient.getBlobClient(azureFilePath + "/" + zipFileName); zipFileClient.upload(new ByteArrayInputStream(outputStream.toByteArray()), outputStream.size(),true);
异常信息
java.util.zip.ZipException: no current ZIP entry at java.base/java.util.zip.ZipOutputStream.write(ZipOutputStream.java:343) ~[na:na] at java.base/java.io.FilterOutputStream.write(FilterOutputStream.java:108) ~[na:na] at main.java.com.azure.filezip.ApiControllerzip.lambda$uploadZip$0(ApiControllerzip.java:78) ~[classes/:na]
解决建议
核心原因:ZipOutputStream并非线程安全
ZipOutputStream的putNextEntry、write、closeEntry等方法没有线程同步机制,多线程同时操作会导致内部状态混乱——比如线程A刚调用putNextEntry,线程B就覆盖了当前entry,线程A再执行write时就找不到对应的entry,直接抛出异常。可行的优化方案:
- 多线程下载+单线程写入Zip流
将文件下载与Zip写入解耦,用多线程并行下载Blob文件到内存,所有文件下载完成后,由主线程统一写入ZipOutputStream,既保留下载的性能优势,又避免多线程操作Zip流的冲突。调整后示例代码:try (ZipOutputStream zipStream = new ZipOutputStream(outputStream)) { List<CompletableFuture<Map.Entry<String, byte[]>>> futures = new ArrayList<>(); for (String singleFileName : fileNames) { String singleFilePath = azureFilePath + "/" + singleFileName.trim(); BlobClient blobClient = blobContainerClient.getBlobClient(singleFilePath); CompletableFuture<Map.Entry<String, byte[]>> future = CompletableFuture.supplyAsync(() -> { try (ByteArrayOutputStream fileOutputStream = new ByteArrayOutputStream()) { if (blobClient.exists()) { blobClient.download(fileOutputStream); return new AbstractMap.SimpleEntry<>(singleFileName, fileOutputStream.toByteArray()); } else { throw new RuntimeException("File not found: " + singleFilePath); } } catch (IOException e) { throw new RuntimeException("Error processing file: " + singleFileName, e); } }); futures.add(future); } // 等待所有文件下载完成,逐个写入Zip流 for (CompletableFuture<Map.Entry<String, byte[]>> future : futures) { Map.Entry<String, byte[]> entry = future.join(); zipStream.putNextEntry(new ZipEntry(entry.getKey())); zipStream.write(entry.getValue()); zipStream.closeEntry(); } } // 上传Zip文件到Blob BlobClient zipFileClient = blobContainerClient.getBlobClient(azureFilePath + "/" + zipFileName); zipFileClient.upload(new ByteArrayInputStream(outputStream.toByteArray()), outputStream.size(), true); - 加全局锁强制串行写入(不推荐)
如果一定要在多线程中写入Zip流,可给zipStream加全局锁,但这样会让写入操作串行化,失去多线程的性能优势,仅作为应急方案:// 在lambda中操作Zip流时加锁 synchronized (zipStream) { zipStream.putNextEntry(new ZipEntry(singleFileName)); zipStream.write(fileOutputStream.toByteArray()); zipStream.closeEntry(); } - 内存优化(针对大文件场景)
如果处理的文件较大,用ByteArrayOutputStream会占用过多内存,建议下载到临时文件,再从临时文件写入ZipOutputStream,避免内存溢出。
- 多线程下载+单线程写入Zip流
内容的提问来源于stack exchange,提问作者Mani
相关产品推荐
相关产品推荐

