首页 > 解决方案 > 将 itext 替换为 pdfbox 性能

问题描述

我正在评估将我们的 pdf 处理从 itext 替换为 pdfbox。我对单页(94KB、469KB、937KB)的 200 个 pdf 进行了一些测试,并将它们合并到我们的应用程序中的一个 pdf 中。PDFBox 版本:2.0.23。文本版本:2.1.7。以下是测试结果:

试验结果

这是itext的实现:

byte[] l_PDFPage = null;
PdfReader l_PDFReader = null;
PdfCopy l_Copier = null;
Document l_PDFDocument = null;
OutputStream l_Stream = new FileOutputStream(m_File);

// do it for all pages in the editor
for( int i = 0; i < m_Editor.getCountOfElements(); i++ ) {
  l_Page = m_Editor.getPageAt(i);
  l_PDFPage = l_Page.getAsPdf();
  l_PDFReader = new PdfReader(l_PDFPage);
  l_PDFReader.getPageN(1).put(PdfName.ROTATE, new PdfNumber(l_PDFReader.getPageRotation(1) + l_Page.getRotation() % 360));
  l_PDFReader.consolidateNamedDestinations();

  if( i == 0 ) {
    l_PDFDocument = new Document(l_PDFReader.getPageSizeWithRotation(1));
    l_Copier = new PdfCopy(l_PDFDocument, l_Stream);
    l_PDFDocument.open();
  }

  l_Copier.addPage(l_Copier.getImportedPage(l_PDFReader, 1));

  if( l_PDFReader.getAcroForm() != null )
    l_Copier.copyAcroForm(l_PDFReader);

  l_Copier.flush();
  l_Copier.freeReader(l_PDFReader);
}

l_PDFDocument.close();
l_Stream.close();

这是pdfbox的实现:

byte[] l_PDFPage = null;
List<PDDocument> pageDocuments = new ArrayList<>();
PDDocument saveDocument = new PDDocument();

try {
  // do it for all pages in the editor
  for( int i = 0; i < m_Editor.getCountOfElements(); i++ ) {
    // our wrapper object for a page
    l_Page = m_Editor.getPageAt(i);
  
    // page as byte[]
    l_PDFPage = l_Page.getAsPdf();
  
    PDDocument document = PDDocument.load(l_PDFPage);
  
    // save page document to close it later
    pageDocuments.add(document);
  
    PDPage page = document.getPage(0);   
    saveDocument.addPage(saveDocument.importPage(page));
  }

  saveDocument.save(l_Stream);
}
finally {
  // close every page document
  for(PDDocument doc : pageDocuments) {
    doc.close();
  }
  
  saveDocument.close();      
}

我也尝试过使用 pdfbox 的 pdfmerger。性能几乎与其他 pdfbox 实现相同。但是对于 937KB 的文件,我使用这个实现在内存不足的异常中运行:

byte[] l_PDFPage = null;
OutputStream l_Stream = new FileOutputStream(m_File);

PDFMergerUtility merger = new PDFMergerUtility();
  
// do it for all pages in the editor
for( int i = 0; i < m_Editor.getCountOfElements(); i++ ) {
  l_Page = m_Editor.getPageAt(i);

  // page as byte[]
  l_PDFPage = l_Page.getAsPdf();
 
  merger.addSource(new ByteArrayInputStream(l_PDFPage));
}

merger.setDestinationStream(l_Stream);
merger.mergeDocuments(null);

所以我的问题:

标签: javapdfbox

解决方案


PDFBox 和 iText 在架构上是不同的,因此在不同的任务中表现不同。

特别是 iText 尝试尽早写出新内容,在您的情况下,大部分页面已在期间写入输出

l_Copier.addPage(l_Copier.getImportedPage(l_PDFReader, 1));

l_PDFDocument.close();

最终只完成 PDF 并写入最后剩余的对象和文件预告片。

另一方面,PDFBox 一次保存所有内容:

saveDocument.save(l_Stream);

iText 的方法的优点是内存占用更小(如您所见),缺点是一旦写入页面数据就无法更改。

(顺便说一句:iText 架构已从 iText 5 更改为 iText 7,在 iText 7 中,您可以选择并且可以将所有内容保存在内存中,但这里的代价也是很大的内存占用。)

因此,

与 itext 相比,为什么 pdfbox 的性能(所需时间和内存使用)如此糟糕?

以上可以部分解释内存使用的差异。同样在 iText 之后

l_Copier.freeReader(l_PDFReader);

可以PdfReader关闭(您留给垃圾收集器为您做的)以释放其资源,而在您的 PDFBox 代码中,您保持所有源文档打开,将资源保留到最后。(实际上我会假设当您使用 时importPage,您不需要保留它们。)

关于时间我现在不确定。您应该做一些更精细的计时,并确定在 PDFBox 中究竟在哪里使用了额外的时间;因此,我支持@Tilman 的分析数据请求。我认为这是在最后一次保存期间,但这只是一种预感。此外,此类时间差异可能取决于相关 PDF 的结构细节,并且对于其他文档可能不那么极端。


推荐阅读