【问题标题】:Using StAX to create index for XML for quick access使用 StAX 为 XML 创建索引以便快速访问
【发布时间】:2017-09-08 01:24:54
【问题描述】:

有没有办法使用 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) -&gt; (offset in file)

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


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

我找不到只需要 XSD 来创建内存数据库的数据库,它不使用那么多 RAM。这一切都是为服务器设计的,或者需要定义模式和映射 XML。所以我认为它只是不存在。

【问题讨论】:

  • 为什么不是内存数据库? XML 是一种存储信息的可怕方式。
  • 你试过r.getLocation().getCharacterOffset()吗?
  • 内存中使用的内存是未压缩的 xml 文件的 3 倍。这对于桌面应用程序来说有点太多了。所以我想使用索引访问。
  • 真的吗?您尝试了哪个内存数据库?氢2? SQLite? XML 作为一种传输格式是可以接受的,但作为一种存储格式(带有操作),它真的是纯粹的垃圾。
  • @jschnasse:是的,但 javadoc 声明:“位置提供的所有信息都是可选的。例如,应用程序可能只报告行号。”我得到了光标的位置,但是是当前元素的位置。它已经读到下一个元素。这不是导致START_ELEMENT的元素的开头。

标签: java xml jaxb stax random-access


【解决方案1】:

您可以使用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。与 Listener 挂钩以收集字符偏移量

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

【讨论】:

  • ANTLRInputStream 已被弃用,但这适用于CharStreams.fromPath。我仍然需要检查是否可以使用 Jaxb 的偏移量来加载元素而无需读取完整的文件。到目前为止,这看起来很有希望,它是我关于如何获取元素偏移量的问题的答案。
  • 问题似乎出在 ANTLRIputStream 中。使用 ANTLRFileStream(也已弃用)它似乎可以工作,即使对于 17GB 文件也是如此。仍然必须增加堆大小。我会看看 CharStreams。
  • 有效!我需要解析的数据并没有那么大。我使用大约 100MB 的文件进行测试。您已经使用了start.getStartIndex()。但我还需要长度,即:ctx.stop.getStartIndex() - ctx.start.getStartIndex() + 1。我制作了自己的InputStream,它也有“限制”,而不仅仅是“跳过”。然后我需要创建一个StreamSource 并使用我的自定义流(setInputStream),然后我只需使用XMLStreamReader 来解组我的对象。我显然知道类型,因为我在 XML 文件中查找某些元素。在我的系统上,每个元素的解组只需要 0.3 秒。
  • 我编辑了我的答案。提供的代码适用于 17GB 维基百科转储。在我的 github 帐户下,我提供了一个完整的示例,该示例使用 JAXB 从字符偏移中读取前 50 个 &lt;page&gt; 元素到 Java 对象。
  • 0 反对票 使用 antlr 生成解析器非常棒。生成的解析器不仅提供了我需要的偏移量,它还被证明是一个完美的通用解析器。干得好!
【解决方案2】:

我只是不得不解决这个问题,并且花了太多时间来解决这个问题。希望下一个来寻找想法的可怜人能从我的痛苦中受益。

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

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

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

我为此创建了一个库,位于 GitHubMaven Central。如果您只想获取重要信息,派对技巧在ByteTrackingReader

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-16
    • 1970-01-01
    • 1970-01-01
    • 2020-10-19
    • 2013-07-18
    相关资源
    最近更新 更多