【问题标题】:PDFbox saying PDDocument closed when its notPDFbox说PDDocument在没有关闭时关闭
【发布时间】:2018-10-03 00:06:54
【问题描述】:

我正在尝试使用 PDFbox 填充重复的表单。我正在使用 TreeMap 并使用单独的记录填充表单。 pdf 表单的格式是这样的,第一页列出了六条记录,第二页插入了一个静态页面。 (对于大于六个记录的 TreeMap,该过程重复)。我得到的错误特定于 TreeMap 的大小。这就是我的问题。我不明白为什么当我用超过 35 个条目填充 TreeMap 时会收到以下警告:

2018 年 4 月 23 日上午 2:36:25 org.apache.pdfbox.cos.COSDocument 完成 警告:警告:您没有关闭 PDF 文档

public class test {
    public static void main(String[] args) throws IOException,         IOException {
    // TODO Auto-generated method stub
    File dataFile = new File("dataFile.csv");
    File fi = new File("form.pdf");
    Scanner fileScanner = new Scanner(dataFile);
    fileScanner.nextLine();
    TreeMap<String, String[]> assetTable = new TreeMap<String, String[]>();
    int x = 0;
    while (x <= 36) {
        String lineIn = fileScanner.nextLine();
        String[] elements = lineIn.split(",");
        elements[0] = elements[0].toUpperCase().replaceAll(" ", "");
        String key = elements[0];
        key = key.replaceAll(" ", "");
        assetTable.put(key, elements);
        x++;
    }
    PDDocument newDoc = new PDDocument();
    int control = 1;
    PDDocument doc = PDDocument.load(fi);
    PDDocumentCatalog cat = doc.getDocumentCatalog();
    PDAcroForm form = cat.getAcroForm();
    for (String s : assetTable.keySet()) {
        if (control <= 6) {
            PDField IDno1 = (form.getField("IDno" + control));
            PDField Locno1 = (form.getField("locNo" + control));
            PDField serno1 = (form.getField("serNo" + control));
            PDField typeno1 = (form.getField("typeNo" + control));
            PDField maintno1 = (form.getField("maintNo" + control));
            String IDnoOne = assetTable.get(s)[1];
            //System.out.println(IDnoOne);
            IDno1.setValue(assetTable.get(s)[0]);
            IDno1.setReadOnly(true);
            Locno1.setValue(assetTable.get(s)[1]);
            Locno1.setReadOnly(true);
            serno1.setValue(assetTable.get(s)[2]);
            serno1.setReadOnly(true);
            typeno1.setValue(assetTable.get(s)[3]);
            typeno1.setReadOnly(true);
            String type = "";
            if (assetTable.get(s)[5].equals("1"))
                type += "Hydrotest";
            if (assetTable.get(s)[5].equals("6"))
                type += "6 Year Maintenance";
            String maint = assetTable.get(s)[4] + " - " + type;
            maintno1.setValue(maint);
            maintno1.setReadOnly(true);
            control++;
        } else {
            PDField dateIn = form.getField("dateIn");
            dateIn.setValue("1/2019 Yearlies");
            dateIn.setReadOnly(true);
            PDField tagDate = form.getField("tagDate");
            tagDate.setValue("2019 / 2020");
            tagDate.setReadOnly(true);
            newDoc.addPage(doc.getPage(0));
            newDoc.addPage(doc.getPage(1));
            control = 1;
            doc = PDDocument.load(fi);
            cat = doc.getDocumentCatalog();
            form = cat.getAcroForm();
        }
    }
    PDField dateIn = form.getField("dateIn");
    dateIn.setValue("1/2019 Yearlies");
    dateIn.setReadOnly(true);
    PDField tagDate = form.getField("tagDate");
    tagDate.setValue("2019 / 2020");
    tagDate.setReadOnly(true);
    newDoc.addPage(doc.getPage(0));
    newDoc.addPage(doc.getPage(1));
    newDoc.save("PDFtest.pdf");
    Desktop.getDesktop().open(new File("PDFtest.pdf"));

}

我终其一生都无法弄清楚我做错了什么。这是我使用 PDFbox 的第一周,所以我希望它是简单的。

更新的错误信息

WARNING: Warning: You did not close a PDF Document
Exception in thread "main" java.io.IOException: COSStream has been closed and cannot be read. Perhaps its enclosing PDDocument has been closed?
    at org.apache.pdfbox.cos.COSStream.checkClosed(COSStream.java:77)
    at org.apache.pdfbox.cos.COSStream.createRawInputStream(COSStream.java:125)
    at org.apache.pdfbox.pdfwriter.COSWriter.visitFromStream(COSWriter.java:1200)
    at org.apache.pdfbox.cos.COSStream.accept(COSStream.java:383)
    at org.apache.pdfbox.cos.COSObject.accept(COSObject.java:158)
    at org.apache.pdfbox.pdfwriter.COSWriter.doWriteObject(COSWriter.java:522)
    at org.apache.pdfbox.pdfwriter.COSWriter.doWriteObjects(COSWriter.java:460)
    at org.apache.pdfbox.pdfwriter.COSWriter.doWriteBody(COSWriter.java:444)
    at org.apache.pdfbox.pdfwriter.COSWriter.visitFromDocument(COSWriter.java:1096)
    at org.apache.pdfbox.cos.COSDocument.accept(COSDocument.java:419)
    at org.apache.pdfbox.pdfwriter.COSWriter.write(COSWriter.java:1367)
    at org.apache.pdfbox.pdfwriter.COSWriter.write(COSWriter.java:1254)
    at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1232)
    at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1204)
    at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1192)
    at test.test.main(test.java:87)

【问题讨论】:

  • 还应该注意,while循环中的x值是我限制TreeMap大小的原因。
  • 您将资源从一个文档传递到另一个文档,然后“丢失”对源文档的引用(在第二个 doc = PDDocument.load(fi); 中),因此它被 gc 关闭。不要那样做。确保您的文档在使用其资源时保持打开状态,并明确关闭。

标签: java pdfbox treemap


【解决方案1】:

警告本身

你似乎得到了错误的警告。它说:

警告:您没有关闭 PDF 文档

因此,与您的想法相反,“PDFbox 说 PDDocument 没有关闭”,PDFBox 说您确实没有关闭了文档!

在您进行编辑后,您发现它实际上说 COSStream 已关闭,而可能原因是封闭的PDDocument 已经关闭。这只是一种可能!

你的情况的警告

话虽如此,通过将一个文档中的页面添加到另一个文档中,您最终可能会引用两个文档中的这些页面。在这种情况下,在关闭两个文档的过程中(例如,通过垃圾收集自动关闭),第二个关闭可能确实会偶然发现一些已经关闭的 COSStream 实例。

所以我的第一个建议是在末尾关闭文档

doc.close();
newDoc.close();

可能不会删除警告,只是改变它们的时间。

实际上,您不仅创建了两个文档docnewDoc,甚至还创建了新的PDDocument 实例并将它们一次又一次地分配给doc,在该过程中将以前的文档对象设置在该变量中免费用于垃圾收集。因此,一旦不再引用,您最终就会关闭一大堆文档。

我认为提前关闭doc 中的所有这些文档并不是一个好主意,尤其是在保存newDoc 之前。

但是,如果您的代码最终将作为大型应用程序的一部分而不是作为小型一次性测试应用程序运行,则您应该在某些 Collection 中收集所有这些 PDDocument 实例并在保存后立即显式关闭它们newDoc 然后清空收藏。

实际上,您的异常看起来像那些丢失的PDDocument 实例之一已被垃圾回收关闭,因此即使使用简单的一次性实用程序,您也应该收集文档以防止它们被 GC 处理。

(@Tilman,如果我错了,请纠正我......)

导入页面

为防止不同文档共享页面出现问题,您可以尝试将页面导入到目标文档,然后将导入的页面添加到目标文档页面树中。 IE。替换

newDoc.addPage(doc.getPage(0));
newDoc.addPage(doc.getPage(1));

通过

newDoc.addPage(newDoc.importPage(doc.getPage(0)));
newDoc.addPage(newDoc.importPage(doc.getPage(1)));

这应该允许您在丢失之前关闭doc 中的每个PDDocument 实例。 但是,这有一些缺点,参见。方法 JavaDoc 和 this answer here

代码中的一个实际问题

在您的合并文档中,您将有许多具有相同名称的字段(至少在您的 CSV 文件中有足够多的条目的情况下),这些字段最初设置为不同的值。您可以从各自原始文档的PDAcroForm 访问字段,但不要将它们添加到组合结果文档的PDAcroForm

这是自找麻烦! PDF 格式确实认为表单是文档范围的,所有字段都从文档的 AcroForm 字典中引用(直接或间接),并且它希望具有相同名称的字段有效地成为同一字段的不同可视化,因此都具有相同的值。

因此,PDF 处理器可能会以意想不到的方式处理您的文档字段,例如

  • 通过在具有相同名称的所有字段中显示相同的值(因为它们应该具有相同的值)或
  • 忽略您的字段(因为它们不在文档 AcroForm 结构中)。

特别是,以编程方式读取 PDF 字段值将失败,因为在这种情况下,表单最终被视为文档范围并基于 AcroForm。另一方面,PDF 查看器可能会首先显示您的设置值并让外观正常。

为防止这种情况,您应该在合并之前重命名字段。您可以考虑使用PDFMergerUtility,它在后台进行了这样的重命名。有关该实用程序类的示例用法,请查看PDFMergerExample

【讨论】:

  • 对不起,我应该复制更多的错误信息。它继续说 PDDocument 已经关闭。当我回到那台电脑前时,我会复制并粘贴确切的措辞。
  • “继续说 PDDocument 已经关闭。” - 不,它说 COSStream 已经关闭。 可能的原因是封闭的PDDocument 已经关闭。这只是一个猜想!话虽如此,通过将一个文档中的页面添加到另一个文档中,您最终可能会引用两个文档中的这些页面。在这种情况下,在关闭两个文档的过程中,第二个关闭可能确实会偶然发现一些已经关闭的COSStream 实例。
  • 感谢您提供的所有详细信息!非常感谢。如果我改变了很多主题,请原谅我,但是......在您看来,我将 PDPage 转换为图像然后添加它会更有利吗?这样我就可以完全消除字段名称问题。生成此报告后,目的是打印并完成,无需再次编辑。如果是这样,您能否提供一些指导,说明我可以在哪里阅读?这是我写的第一个供他人使用的程序,请原谅我的幼稚。
  • 我的最终解决方案远非最谨慎的方法。由于很明显该对象很快就被垃圾收集器拾取,因此我只是将该对象添加到该对象的每个实例的 PDDocuments 数组中。我知道这不是好的做法!我还查看了潜在的内存使用情况,最多使用了几 MB 额外的内存。该解决方案在我的情况下运行良好,仅仅是因为预期用途。谢谢大家的帮助!
  • @Ken “我只是将对象添加到对象的每个实例的 PDDocuments 数组中。我知道这不是好的做法!” - 这还不错。您需要这些对象不会被垃圾收集,因此您需要对它们进行引用。我会使用一些集合类,但这可能是个人喜好的情况......
【解决方案2】:

即使上面的答案被标记为问题的解决方案,由于解决方案隐藏在 cmets 中,我想在这个级别添加这个答案。我花了几个小时寻找解决方案。

我的代码 sn-ps 和 cmets。

// Collection solely for purpose of preventing premature garbage collection
List<PDDocument> sourceDocuments = new ArrayList<>( );

...

// Source document (actually inside a loop)
PDDocument docIn = PDDocument.load( artifactBytes );

// Add document to collection before using it to prevent the problem
sourceDocuments.add( docIn );

// Extract from source document 
PDPage extractedPage = docIn.getPage( 0 );
// Add page to destination document
docOut.addPage( extractedPage );

...

// This was failing with "COSStream has been closed and cannot be read."
// Now it works.
docOut.save( bundleStream );

【讨论】:

    猜你喜欢
    • 2017-03-14
    • 2019-01-11
    • 2023-04-09
    • 2020-05-19
    • 2013-08-17
    • 2019-04-16
    • 1970-01-01
    • 2017-03-26
    • 2017-01-22
    相关资源
    最近更新 更多