如何使用Apache POI标准加密加密2GB大体积PPTX文件?
Apache POI加密大体积PPTX(2GB含嵌入式视频)的问题与解决方法
问题描述
尝试用Apache POI对2GB的PPTX文件(内含大量嵌入式视频)进行标准密码加密,已配置大文件相关参数:
IOUtils.setByteArrayMaxOverride(Integer.MAX_VALUE); ZipSecureFile.setMinInflateRatio(-1.0); ZipInputStreamZipEntrySource.setThresholdBytesForTempFiles(50000000); ZipPackage.setUseTempFilePackageParts(true);
但仍抛出数组大小超出限制的异常:
Exception in thread "main" org.apache.poi.util.RecordFormatException: Can't allocate an array > 2147483647 at org.apache.poi.util.IOUtils.safelyAllocateCheck(IOUtils.java:559) at org.apache.poi.util.IOUtils.safelyAllocate(IOUtils.java:547) at org.apache.poi.poifs.nio.ByteArrayBackedDataSource.extend(ByteArrayBackedDataSource.java:86) at org.apache.poi.poifs.nio.ByteArrayBackedDataSource.write(ByteArrayBackedDataSource.java:63) at org.apache.poi.poifs.filesystem.POIFSFileSystem.createBlockIfNeeded(POIFSFileSystem.java:493) at org.apache.poi.poifs.filesystem.POIFSStream$StreamBlockByteBuffer.createBlockIfNeeded(POIFSStream.java:272) at org.apache.poi.poifs.filesystem.POIFSStream$StreamBlockByteBuffer.write(POIFSStream.java:294) at org.apache.poi.poifs.filesystem.DocumentOutputStream.write(DocumentOutputStream.java:144) at org.apache.poi.poifs.filesystem.DocumentOutputStream.write(DocumentOutputStream.java:124) at java.base/java.io.FilterOutputStream.write(FilterOutputStream.java:87) at java.base/java.io.FilterOutputStream.write(FilterOutputStream.java:137) at org.apache.poi.util.LittleEndianOutputStream.write(LittleEndianOutputStream.java:89) at org.apache.poi.util.IOUtils.copy(IOUtils.java:410) at org.apache.poi.util.IOUtils.copy(IOUtils.java:387) at org.apache.poi.poifs.crypt.standard.StandardEncryptor$StandardCipherOutputStream.processPOIFSWriterEvent(StandardEncryptor.java:201) at org.apache.poi.poifs.filesystem.POIFSDocument.<init>(POIFSDocument.java:112) at org.apache.poi.poifs.filesystem.DirectoryNode.createDocument(DirectoryNode.java:380) at org.apache.poi.poifs.crypt.standard.StandardEncryptor$StandardCipherOutputStream.writeToPOIFS(StandardEncryptor.java:185) at org.apache.poi.poifs.crypt.standard.StandardEncryptor$StandardCipherOutputStream.close(StandardEncryptor.java:175) at Program.encryptPPTXFile(Program.java:108)
原始加密代码如下:
try(POIFSFileSystem fs = new POIFSFileSystem()) { EncryptionInfo info = new EncryptionInfo(EncryptionMode.standard); Encryptor encryptor = info.getEncryptor(); encryptor.confirmPassword(password); // Read in an existing OOXML file and write to encrypted output stream try (OPCPackage opc = OPCPackage.open(inputPath); OutputStream os = encryptor.getDataStream(fs)) { opc.save(os); } // Write out the encrypted version try (FileOutputStream fos = new FileOutputStream(outputPath)) { fs.writeFilesystem(fos); } System.out.println("Document successfully encrypted"); } catch (IOException | GeneralSecurityException | InvalidFormatException e) { e.printStackTrace(); }
问题核心原因
POIFSFileSystem默认采用内存字节数组存储加密后的全部内容,当文件体积超过2GB时,会触发Java中int类型的最大值限制(2147483647字节),无法分配足够大的内存数组。即使设置了IOUtils.setByteArrayMaxOverride(Integer.MAX_VALUE),也无法突破JVM对单个数组的大小限制,且加密过程的额外内存开销会进一步加剧该问题。
解决方法
改用基于磁盘文件的NPOIFSFileSystem替代内存版的POIFSFileSystem,将加密过程的中间数据存储到临时磁盘文件中,避免内存过载。同时调整OPCPackage的打开模式,减少内存占用。
修改后的加密代码
try (File tempFile = File.createTempFile("poifs", ".tmp"); NPOIFSFileSystem fs = new NPOIFSFileSystem(tempFile, true)) { EncryptionInfo info = new EncryptionInfo(EncryptionMode.standard); Encryptor encryptor = info.getEncryptor(); encryptor.confirmPassword(password); // 以文件模式打开OPCPackage,避免加载整个文件到内存 try (OPCPackage opc = OPCPackage.open(new File(inputPath), PackageAccess.READ_WRITE); OutputStream os = encryptor.getDataStream(fs)) { opc.save(os); } // 输出加密后的文件 try (FileOutputStream fos = new FileOutputStream(outputPath)) { fs.writeFilesystem(fos); } System.out.println("Document successfully encrypted"); } catch (IOException | GeneralSecurityException | InvalidFormatException e) { e.printStackTrace(); }
额外优化建议
- 调大临时文件阈值:将
ZipInputStreamZipEntrySource.setThresholdBytesForTempFiles设置为更大值(如10010241024),进一步减少小文件的内存占用。 - 使用64位JVM:确保运行环境为64位Java虚拟机,提升JVM可分配内存上限,避免其他内存不足异常。
- 清理临时文件:可在
finally块中主动删除临时文件,或使用tempFile.deleteOnExit()确保JVM关闭时清理临时文件。
内容的提问来源于stack exchange,提问作者harsimranb
相关产品推荐
相关产品推荐

