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

有没有办法使用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 = 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来创建内存数据库的数据库,它不会使用那么多的RAM。 它全部用于服务器,或者需要定义模式并映射XML。 所以我认为它不存在。

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

以下在~17GB维基百科转储 /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 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 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 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); } } 

在github上找到完整的例子 。