巨大的XML文件到文本文件

时间:2014-03-07 04:59:21

标签: java xml

我有一个巨大的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(有效)。现在,我使用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行。有没有更好的方法来改善性能..?

6 个答案:

答案 0 :(得分:5)

我可以看到一些可能对您有帮助的解决方案:

  1. 使用BufferedInputStream而不是简单的FileInputStream来减少磁盘操作次数
  2. 考虑使用StringBuilder创建pageContent而不是String catenation。
  3. 增加Java堆(-Xmx选项),以防您的内存与2GB示例绑定。
  4. 在这样的情况下,连接代码分析器(例如Java VisualVM)会非常有趣,因为您可以确切地看到代码中调用速度很慢的方法。然后,您可以适当地关注优化。

答案 1 :(得分:2)

如果解析XML文件是主要问题,请考虑使用VTD-XML,即扩展版本,因为它支持最大256GB的文件。

由于它基于非提取文档解析,因此它具有很高的内存效率,并且使用它来使用XPath查询/提取文本也非常快。您可以从here了解有关此方法和VTD-XML的更多详细信息。

答案 2 :(得分:1)

尝试使用SAX解析器进行解析,因为DOM会尝试解析整个内容并将其放在内存中。因此,您将获得内存异常。 SAX解析器不会在一段时间内解析整个内容。

答案 3 :(得分:1)

什么是pageContent?它似乎是String。立即进行的一个简单优化是使用StringBuilder代替;它可以附加字符串,而不必像String s +=那样创建字符串的全新副本(你也可以使用初始保留容量构建它,以减少内存重新分配和副本,如果你有一个想法开头的长度)。

连接String是一个缓慢的操作,因为字符串在Java中是不可变的;每次拨打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限制,则在不同的物理磁盘上选择输出文件可能有所帮助。

答案 4 :(得分:0)

您的代码看起来很标准。 但是,您是否可以尝试将FileInputStream包装到BufferedInputStream中,如果有帮助,请告诉我们? BufferedInputstream可以为您节省很少的本机调用,因此有可能获得更好的性能。 您必须使用缓冲区大小来获得最佳性能。根据您的JVM内存分配设置一些大小。

答案 5 :(得分:0)

  1. BufferedInputStream
  2. 周围使用FileInputStream.
  3. 不要连接数据。这完全是浪费时间和空间,可能会占用很大的空间。你马上把它写出来。在BufferedWriter周围使用FileWriter即可。