试图了解从 Apache poi 保存对 Word 文档的更改

Trying to understand the saving of changes to a Word doc from Apache poi

我有一个Word文档(docx);我想对该文档进行更改并将结果另存为另一个文件,将原始文件留在原处。我有以下代码说明我当前的问题:

package sandbox.word.doccopy;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;

public class CopyTest
{
  public static void main(String[] args) throws Exception
  {
    String sourceFilename      = "CopyTestSource.docx";
    String destinationFilename = "CopyTestResult.docx";
    
    CopyTest docCopy = new CopyTest();
    docCopy.copyTesting(sourceFilename, destinationFilename);
    System.out.println("done");
  }
  
  public void copyTesting(String source, String destination)
      throws IOException, InvalidFormatException
  {
    XWPFDocument doc = new XWPFDocument(OPCPackage.open(source));
    // for each paragraph that has runs, 
    // put an exclamation at the end of the first run.
    for (XWPFParagraph par : doc.getParagraphs())
    {
      List<XWPFRun> runs = par.getRuns();
      if (runs.size() > 0) 
      { XWPFRun run = par.getRuns().get(0);
        String text = run.getText(0);
        text = text + "!";
        run.setText(text, 0);
      }
    }
    
//    FileOutputStream fos = new FileOutputStream(destination);
//    doc.write(fos);
//    fos.close();
    doc.close();
  }
  
}

我已经 运行 了三种方法,更改 class 文件底部的注释行。如您所见,有 3 行使用目标文件名创建文件输出流、写入并关闭它,还有 1 行仅关闭当前文档。

如果我注释掉第 3 行并保留第 1 行,则不会向当前文档写入任何更改(当然,也不会创建副本文档)。

如果我保留所有 4 行未注释,则会创建带有更改的副本文档,并且更改也会写入源文档。

如果我注释掉第 4 行,我会得到一个有更改的目标文档,而源文档保持不变。

最后一个是我想要的,我可以编写我的代码来做到这一点。但我希望在更改后关闭文档会更改它或不更改它,并且更改它不会取决于我是否将更改写入另一个文件。

任何人都可以阐明这一点吗?

罪魁祸首是:XWPFDocument doc = new XWPFDocument(OPCPackage.open(source));。特别是这个:OPCPackage.open(source).

虽然 static OPCPackage open(java.lang.String path) OPCPackage 从文件路径 path 的基础文件中以 read/write 权限打开。另外,它直接连接到底层文件。这可以节省一些内存,但也有缺点,正如您现在将要看到的那样。

XWPFDocument 中的所有更改都在 OPCPackage 中进行,但首先在随机存取存储器中进行。

在调用 doc.write 时调用 POIXMLDocument.write(java.io.OutputStream stream),首先更新基础 OPCPackage。然后通过给定的 OutputStream stream 将更改的 OPCPackage 保存在目标文档中。因此,如果不调用 doc.write,文件中的任何内容都不会发生变化,只会保留在随机存取存储器中。

然后在 doc.close() 被调用的同时 OPCPackage.close 也被调用。这将关闭打开的可写包并保存其内容。由于 OPCPackage 直接连接到底层文件,它将内容保存到该文件中。这就是将更改也写入源文档的原因。

这应该可以解释您的观察结果。

XWPFDocument还提供了构造函数 XWPFDocument(java.io.InputStream is). This internally calls OPCPackage.open(java.io.InputStream in)。这将打开 InputStream 中的 OPCPackage。然后 OPCPackage 仅在随机存取存储器中,并且独立于源文件。这会使用更多内存,因为整个 OPCPackage 需要在随机存取内存中,但 OPCPackage.close 不会导致源文件发生变化。

所以我会做的是:

...
XWPFDocument doc = new XWPFDocument(new FileInputStream(source));
...
FileOutputStream fos = new FileOutputStream(destination);
doc.write(fos);
fos.close();
doc.close();
...