
在 java 应用中,处理大量数据或执行复杂操作时,outofmemoryerror: java heap space 是一个常见的运行时错误。对于 itext 库进行 pdf 合并的场景,尤其当需要合并的文件数量较多或单个文件体积较大时,如果处理不当,极易耗尽 jvm 堆内存。
问题的核心在于现有实现中对 ByteArrayOutputStream 的使用。ByteArrayOutputStream 会将所有写入的数据缓存在内存中,直到调用 toByteArray() 方法时,它会返回一个包含所有数据的字节数组。这意味着,如果合并后的 PDF 文件大小为 100MB,那么在内存中就会存在一个至少 100MB 的字节数组。当需要合并的 PDF 总大小超出 JVM 分配的堆内存限制时,就会抛出 OutOfMemoryError。尽管原始代码中对 PdfReader 进行了及时的 freeReader 和 close 操作,但最终合并结果的内存占用依然是瓶颈。
原始代码示例:
public static byte[] mergePdf(List < InputStream > inputStreams) {
Document document = new Document();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 内存瓶颈
PdfCopy copy = new PdfSmartCopy(document, byteArrayOutputStream);
document.open();
for (InputStream inputStream: inputStreams) {
PdfReader pdfReader = new PdfReader(inputStream);
copy.addDocument(pdfReader);
copy.freeReader(pdfReader);
pdfReader.close();
}
document.close();
return byteArrayOutputStream.toByteArray(); // 最终将所有数据加载到内存
}解决 OutOfMemoryError 的关键在于避免在内存中一次性持有整个合并后的 PDF 文件。最佳实践是采用流式处理,直接将合并结果写入到最终的目标 OutputStream。这意味着,合并过程中的数据会直接从输入流读取,经过 iText 处理后,直接写入到指定的输出流,而不会在 JVM 堆内存中累积一个完整的 PDF 字节数组。
这种方法尤其适用于以下场景:
为了实现直接流式输出,我们需要修改 mergePdf 方法的签名,使其接受一个 OutputStream 参数,并将 PdfCopy 的目标指向这个传入的 OutputStream。
import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.PdfCopy;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSmartCopy;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.util.List;
public class PdfMergeUtil {
/**
* 合并多个PDF输入流到一个指定的输出流中,避免内存溢出。
*
* @param inputStreams 包含待合并PDF内容的输入流列表。
* @param outputStream 目标输出流,合并后的PDF将直接写入此流。
* @throws IOException 如果在读写PDF时发生IO错误。
*/
public static void mergePdf(List<InputStream> inputStreams, OutputStream outputStream) throws IOException {
Document document = new Document();
PdfCopy copy = null; // 使用PdfCopy或PdfSmartCopy
try {
// PdfSmartCopy 优化了对相似资源的共享,可以减小最终文件大小
copy = new PdfSmartCopy(document, outputStream);
document.open();
for (InputStream inputStream : inputStreams) {
PdfReader pdfReader = null;
try {
pdfReader = new PdfReader(inputStream);
copy.addDocument(pdfReader);
// 释放PdfReader资源,防止内存泄漏
copy.freeReader(pdfReader);
} finally {
// 确保PdfReader和其底层InputStream被关闭
if (pdfReader != null) {
pdfReader.close();
}
if (inputStream != null) {
inputStream.close(); // 关闭传入的InputStream
}
}
}
} finally {
// 确保Document和PdfCopy被关闭,完成PDF写入
if (document.isOpen()) {
document.close();
}
// 不需要关闭传入的outputStream,由调用者负责
}
}
// 示例用法:
public static void main(String[] args) {
// 假设这里有一些InputStream代表PDF文件
List<InputStream> pdfInputStreams = List.of(
// new FileInputStream("path/to/file1.pdf"),
// new FileInputStream("path/to/file2.pdf")
// ... 实际应用中替换为真实InputStream
);
// 示例1: 将合并结果写入文件
// try (OutputStream fos = new FileOutputStream("merged_output.pdf")) {
// mergePdf(pdfInputStreams, fos);
// System.out.println("PDFs merged to merged_output.pdf successfully!");
// } catch (IOException e) {
// e.printStackTrace();
// }
// 示例2: 在Web应用中直接写入HttpServletResponse的OutputStream
// 在Servlet或Spring Controller中:
// response.setContentType("application/pdf");
// response.setHeader("Content-Disposition", "attachment; filename=\"merged.pdf\"");
// try (OutputStream os = response.getOutputStream()) {
// mergePdf(pdfInputStreams, os);
// } catch (IOException e) {
// e.printStackTrace();
// }
}
}通过将 iText PDF 合并的输出从 ByteArrayOutputStream 切换到直接流式写入目标 OutputStream,我们可以有效地避免因合并大文件而导致的 OutOfMemoryError。这种策略不仅提升了应用的内存效率和稳定性,也使得合并后的 PDF 能够更灵活地被处理,例如直接作为 HTTP 响应返回给客户端,或直接写入文件,无需在服务器内存中进行昂贵的完整副本存储。在开发涉及大量 PDF 操作的应用时,这种内存优化是至关重要的。
以上就是iText PDF 合并:优化内存使用,避免 OutOfMemoryError的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号