大型XML文件转换为文本文件

9
我是一个有用的助手,可以翻译文本。

我有一个巨大的 XML 文件(15 GB)。我想将 XML 文件中的“text”标签转换为单个页面。

示例 XML 文件:

<root>
    <page>
        <id> 1 </id>
        <text>
        .... 1000 to 50000 lines of text
        </text>
    </page>
    ... Like wise 2 Million `page` tags
</root>

我最初使用了DOM解析器,但它会抛出JAVA OUT OF MEMORY(Valid)错误。现在,我已经使用STAX编写了JAVA代码。它可以正常运行,但性能非常慢。
这是我编写的代码:
 XMLEventReader xMLEventReader = XMLInputFactory.newInstance().createXMLEventReader(new FileInputStream(filePath));
    while(xMLEventReader.hasNext()){
      xmlEvent = xMLEventReader.nextEvent();

    switch(xmlEvent.getEventType()){
    case XMLStreamConstants.START_ELEMENT:
    if( element == "text")
      isText    = true;
    break;
    case XMLStreamConstants.CHARACTERS:
      chars = (Characters) xmlEvent;
      if(! (chars.isWhiteSpace() || chars.isIgnorableWhiteSpace()))
               if(isText)
              pageContent += chars.getData() + '\n';
      break;
    case XMLStreamConstants.END_ELEMENT:
      String elementEnd = (((EndElement) xmlEvent).getName()).getLocalPart();
      if( elementEnd == "text" )
      {
          createFile(id, pageContent);
          pageContent = "";
          isText = false;
      }
      break;
    }
}

这段代码工作得很好(忽略任何小错误)。根据我的理解,XMLStreamConstants.CHARACTERS枚举逐行迭代文本标签。如果TEXT标签中有10000行,则XMLStreamConstants.CHARACTERS将迭代下一行10000次。是否有更好的方法来提高性能..?

只是出于好奇,你目前加载和解析那个文件需要多长时间? - Jason C
我已经解析了2GB的文件。花费了35分钟。 - user1919035
4
pageContent 是什么?它是一个 String 吗?如果是,一个简单的优化方式是立即使用 StringBuilder,它可以附加字符串而无需像 String+= 那样完全创建新的字符串副本(你还可以使用初始保留容量来构造它,以减少内存重新分配和复制,如果你已经有了长度的想法)。 - Jason C
这是维基百科转储文件吗? - Some guy
1
为什么不使用现有的库来提取内容?WikiXMLJ 是一个非常好的库 https://code.google.com/p/wikixmlj/。 - Some guy
显示剩余6条评论
6个回答

4

我可以看到一些可能有助于解决问题的解决方案:

  1. 使用BufferedInputStream而不是简单的FileInputStream来减少磁盘操作。
  2. 考虑使用StringBuilder创建您的pageContent而不是字符串连接。
  3. 增加您的Java堆大小(-Xmx选项),以防您的2GB示例受内存限制。

在这种情况下,连接代码分析器(例如Java VisualVM)会非常有趣,因为您可以精确地看到代码中哪些方法调用较慢。 然后,您可以适当地进行优化。


2
如果解析XML文件是主要问题,请考虑使用VTD-XML,特别是扩展版本,因为它支持高达256GB的文件。
由于它基于非提取式文档解析,所以它相当节省内存,并且使用它来使用XPath查询/提取文本也非常快速。您可以从这里了解更多关于此方法和VTD-XML的详细信息。

1

pageContent是什么?它似乎是一个String。一种简单的优化方法是立即使用StringBuilder;它可以追加字符串而无需像String+=那样完全创建新的字符串副本(如果您有一个长度开始的想法,也可以构造具有初始保留容量的StringBuilder以减少内存重新分配和复制)。

在Java中连接String是一种慢操作,因为字符串是不可变的;每次调用a += b都必须分配一个新字符串,将a复制到其中,然后将b复制到其末尾;使每个连接O(n) wrt. 两个字符串的总长度。添加单个字符也是如此。另一方面,当追加时,StringBuilder具有与ArrayList相同的性能特征。所以,在以下情况下使用:

pageContent += chars.getData() + '\n';

请将pageContent更改为StringBuilder,然后执行以下操作:

pageContent.append(chars.getData()).append('\n');

另外,如果您对其中一个字符串的长度上限有猜测,可以将其传递给 StringBuilder 构造函数以分配初始容量并降低内存重新分配和完整复制的可能性。
顺便提一下,另一个选择是完全跳过 StringBuilder 并直接将数据写入输出文件(假设您没有预先处理数据)。如果这样做,并且性能受到 I/O 限制,则选择位于不同物理磁盘上的输出文件可以帮助提高性能。

1
尝试使用SAX解析器进行解析,因为DOM会尝试解析整个内容并将其放入内存中。因此,您会遇到内存异常。SAX解析器不会一次性解析整个内容。

2
他转换到了一个STAX解析器,它将像SAX解析器一样执行。 - Bruce Martin

0
你的代码看起来很标准。 不过,你能否尝试将你的FileInputStream包装成BufferedInputStream,并告诉我们是否有所帮助? BufferedInputstream可以减少对操作系统的一些本地调用,因此可能会有更好的性能表现。 你需要根据你的JVM内存分配情况来调整缓冲区大小以获得最佳性能。设置一些大小,以便进行优化。

实际上,这可以为您节省大量的系统调用,而且默认缓冲区大小为8192几乎在所有情况下都是完全足够的。 - user207421

0
  1. FileInputStream周围使用BufferedInputStream
  2. 不要连接数据。这是完全浪费时间和空间,可能会浪费很多空间。获取数据后立即写出。为此,请在FileWriter周围使用BufferedWriter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接