使用StAX为XML创建索引以便快速访问

时间:2017-04-12 10:11:01

标签: java xml jaxb stax random-access

有没有办法使用StAX和JAX-B创建索引,然后快速访问XML文件?

我有一个大型XML文件,我需要在其中查找信息。这用于桌面应用程序,因此它应该在RAM很少的系统上运行。

所以我的想法是:创建一个索引然后快速访问大文件中的数据。

我无法拆分文件,因为它是一个我希望不加改变地使用的官方联邦数据库。

使用XMLStreamReader我可以快速找到一些元素,然后使用JAXB来解组元素。

    final XMLStreamReader r = xf.createXMLStreamReader(filename, new FileInputStream(filename));
    final JAXBContext ucontext = JAXBContext.newInstance(Foo.class);
    final Unmarshaller unmarshaller = ucontext.createUnmarshaller();
    r.nextTag();

    while (r.hasNext()) {

        final int eventType = r.next();
        if (eventType == XMLStreamConstants.START_ELEMENT && r.getLocalName().equals("foo")
                && Long.parseLong(r.getAttributeValue(null, "bla")) == bla
                ) {
            // JAX-B works just fine:
            final JAXBElement<Foo> foo = unmarshaller.unmarshal(r,Foo.class);
            System.out.println(foo.getValue().getName());
            // But how do I get the offset?
            // cache.put(r.getAttributeValue(null, "id"), r.getCursor()); // ???
            break;
        }
    }

但我无法获得抵消。我想用它来准备索引:
(id of element) -> (offset in file)

然后我应该能够使用偏移量从那里解组:打开文件流,跳过那么多字节,取消编组。 我找不到这样做的图书馆。如果不知道文件光标的位置,我就无法独立完成。 javadoc明确指出有光标,但我无法找到访问它的方法。

编辑:
我只是试图提供一种可以在旧硬件上运行的解决方案,以便人们可以实际使用它。不是每个人都能买得起新的强大电脑。使用StAX我可以在大约2秒内获得数据,这有点长。但它并不需要RAM。它需要300 MB的RAM才能使用JAX-B。对于这样一个简单的任务,使用一些嵌入式数据库系统只需要很多开销。无论如何我都会使用JAX-B。由于wsimport生成的类已经很完美,因此对我来说其他任何东西都是无用的。当我只需要几个时,我只是不想加载300 MB的对象。

我找不到只需要XSD来创建内存数据库的数据库,它不会使用那么多内存数据库。它全部用于服务器,或者需要定义模式和映射XML。所以我认为它并不存在。

2 个答案:

答案 0 :(得分:4)

您可以使用ANTLR4生成的XML解析器。

以下版本在~17GB Wikipedia dump /20170501/dewiki-20170501-pages-articles-multistream.xml.bz2上运行良好,但我必须使用-xX6GB增加堆大小。

1。获取XML语法

cd /tmp
git clone https://github.com/antlr/grammars-v4

2。生成分析器

cd /tmp/grammars-v4/xml/
mvn clean install

3。将生成的Java文件复制到项目

cp -r target/generated-sources/antlr4 /path/to/your/project/gen

4。使用监听器挂接以收集字符偏移量

package stack43366566;

import java.util.ArrayList;
import java.util.List;

import org.antlr.v4.runtime.ANTLRFileStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTreeWalker;

import stack43366566.gen.XMLLexer;
import stack43366566.gen.XMLParser;
import stack43366566.gen.XMLParser.DocumentContext;
import stack43366566.gen.XMLParserBaseListener;

public class FindXmlOffset {

    List<Integer> offsets = null;
    String searchForElement = null;

    public class MyXMLListener extends XMLParserBaseListener {
        public void enterElement(XMLParser.ElementContext ctx) {
            String name = ctx.Name().get(0).getText();
            if (searchForElement.equals(name)) {
                offsets.add(ctx.start.getStartIndex());
            }
        }
    }

    public List<Integer> createOffsets(String file, String elementName) {
        searchForElement = elementName;
        offsets = new ArrayList<>();
        try {
            XMLLexer lexer = new XMLLexer(new ANTLRFileStream(file));
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            XMLParser parser = new XMLParser(tokens);
            DocumentContext ctx = parser.document();
            ParseTreeWalker walker = new ParseTreeWalker();
            MyXMLListener listener = new MyXMLListener();
            walker.walk(listener, ctx);
            return offsets;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] arg) {
        System.out.println("Search for offsets.");
        List<Integer> offsets = new FindXmlOffset().createOffsets("/tmp/dewiki-20170501-pages-articles-multistream.xml",
                        "page");
        System.out.println("Offsets: " + offsets);
    }

}

5。结果

打印:

抵消:[2441,10854,30257,51419 ....

6。从偏移位置读取

为了测试代码,我编写了将每个维基百科页面读入一个java对象的类

@JacksonXmlRootElement
class Page {
   public Page(){};
   public String title;
}

基本上使用此代码

private Page readPage(Integer offset, String filename) {
        try (Reader in = new FileReader(filename)) {
            in.skip(offset);
            ObjectMapper mapper = new XmlMapper();
             mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            Page object = mapper.readValue(in, Page.class);
            return object;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

查找完整的example on github

答案 1 :(得分:1)

我只需要解决这个问题,就花了太多时间来解决它。希望下一个寻求想法的可怜灵魂可以从我的痛苦中受益。

第一个要解决的问题是,大多数XMLStreamReader实现在您要求其当前偏移量时提供的结果都不准确。 Woodstox在这方面似乎是坚如磐石。

第二个问题是您使用的偏移量的实际类型。如果需要使用多字节字符集,则必须使用char偏移量,这意味着使用提供的偏移量从文件中进行随机访问检索将不会非常有效-您不能仅将指针设置为偏移处的文件并开始阅读,您必须通读直到到达偏移处(skipReader的封面下进行此操作),然后开始提取。如果要处理非常大的文件,则意味着文件末尾附近的内容检索太慢。

我最终写了一个FilterReader,它在读取文件时保留了字节偏移量到char偏移量映射的缓冲区。当需要获取字节偏移量时,我们首先向Woodstox请求char偏移量,然后让自定义阅读器告诉我们char偏移量的实际字节偏移量。我们可以从元素的开头和结尾获取字节偏移量,从而提供我们需要输入的内容,并通过将其作为RandomAccessFile打开将其从文件中以手术方式提取出来,这意味着它在文件中的任何位置都非常快。

我为此创建了一个库,它位于GitHubMaven Central上。如果您只想获取重要的信息,请参加ByteTrackingReader

有人对这整件事是个坏主意发表了评论,为什么要这么做呢? XML是一种传输机制,您应该将其导入数据库并使用更合适的工具处理数据。在大多数情况下,这是正确的,但是如果您要构建通过XML进行通信的应用程序或集成,则需要使用工具来分析和操作交换的文件。我每天都会收到请求来验证提要内容,并能够从海量文件中快速提取出一组特定的项目,并且不仅要验证内容,而且还必须验证格式本身。

无论如何,希望这可以节省一个人几个小时,或者至少使他们更接近解决方案。