使用 StAX 为 XML 创建索引以便快速访问
Using StAX to create index for XML for quick 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 秒内获取数据,这有点长。但它不需要内存。它需要 300 MB 的 RAM 才能使用 JAX-B。对于这样一个简单的任务,使用一些嵌入式数据库系统只会带来很多开销。无论如何我都会使用 JAX-B。因为 wsimport 生成的 类 已经很完美了,所以其他任何东西对我来说都是无用的。当我只需要几个对象时,我不想加载 300 MB 的对象。
我找不到只需要 XSD 来创建不使用那么多 RAM 的内存中数据库的数据库。它都是为服务器制作的,或者需要定义模式并映射 XML。所以我认为它根本不存在。
您可以使用 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.从偏移位置读取
为了测试我编写的 class 代码,它在每个维基百科页面中读取一个 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.
我只需要解决这个问题,并且花了太多时间来解决它。希望下一个来寻找想法的可怜人可以从我的痛苦中受益。
第一个要解决的问题是大多数 XMLStreamReader 实现在您询问它们的当前偏移量时提供不准确的结果。 Woodstox 然而在这方面似乎是 rock-solid。
第二个问题是您使用的实际偏移类型。如果您需要使用 multi-byte 字符集,则必须使用 char 偏移量,这意味着使用提供的偏移量从文件中检索 random-access 不会非常有效 - 您不能只设置在您的偏移量处指向文件的指针并开始读取,您必须通读直到到达偏移量(这就是 skip
在 Reader
的幕后所做的),然后开始提取。如果您正在处理非常大的文件,这意味着检索文件末尾附近的内容太慢了。
我最终编写了一个 FilterReader,它在读取文件时保留字节偏移到字符偏移映射的缓冲区。当我们需要获取字节偏移量时,我们首先向 Woodstox 询问 char 偏移量,然后获取自定义 reader 来告诉我们 char 偏移量的实际字节偏移量。我们可以从元素的开头和结尾获取字节偏移量,为我们提供我们需要进入的内容,并通过将文件作为 RandomAccessFile 打开来从文件中手术提取元素,这意味着它在文件中的任何位置都非常快。
我为此创建了一个库,它位于 GitHub and Maven Central. If you just want to get the important bits, the party trick is in the ByteTrackingReader。
有些人评论说这整件事是个坏主意,你为什么要这么做? XML 是一种传输机制,您应该将其导入数据库并使用更合适的工具处理数据。在大多数情况下,这是正确的,但如果您正在构建通过 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> 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 秒内获取数据,这有点长。但它不需要内存。它需要 300 MB 的 RAM 才能使用 JAX-B。对于这样一个简单的任务,使用一些嵌入式数据库系统只会带来很多开销。无论如何我都会使用 JAX-B。因为 wsimport 生成的 类 已经很完美了,所以其他任何东西对我来说都是无用的。当我只需要几个对象时,我不想加载 300 MB 的对象。
我找不到只需要 XSD 来创建不使用那么多 RAM 的内存中数据库的数据库。它都是为服务器制作的,或者需要定义模式并映射 XML。所以我认为它根本不存在。
您可以使用 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.从偏移位置读取
为了测试我编写的 class 代码,它在每个维基百科页面中读取一个 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.
我只需要解决这个问题,并且花了太多时间来解决它。希望下一个来寻找想法的可怜人可以从我的痛苦中受益。
第一个要解决的问题是大多数 XMLStreamReader 实现在您询问它们的当前偏移量时提供不准确的结果。 Woodstox 然而在这方面似乎是 rock-solid。
第二个问题是您使用的实际偏移类型。如果您需要使用 multi-byte 字符集,则必须使用 char 偏移量,这意味着使用提供的偏移量从文件中检索 random-access 不会非常有效 - 您不能只设置在您的偏移量处指向文件的指针并开始读取,您必须通读直到到达偏移量(这就是 skip
在 Reader
的幕后所做的),然后开始提取。如果您正在处理非常大的文件,这意味着检索文件末尾附近的内容太慢了。
我最终编写了一个 FilterReader,它在读取文件时保留字节偏移到字符偏移映射的缓冲区。当我们需要获取字节偏移量时,我们首先向 Woodstox 询问 char 偏移量,然后获取自定义 reader 来告诉我们 char 偏移量的实际字节偏移量。我们可以从元素的开头和结尾获取字节偏移量,为我们提供我们需要进入的内容,并通过将文件作为 RandomAccessFile 打开来从文件中手术提取元素,这意味着它在文件中的任何位置都非常快。
我为此创建了一个库,它位于 GitHub and Maven Central. If you just want to get the important bits, the party trick is in the ByteTrackingReader。
有些人评论说这整件事是个坏主意,你为什么要这么做? XML 是一种传输机制,您应该将其导入数据库并使用更合适的工具处理数据。在大多数情况下,这是正确的,但如果您正在构建通过 XML 进行通信的应用程序或集成,则需要工具来分析和操作交换的文件。我每天都会收到验证提要内容的请求,能够从大量文件中快速提取一组特定的项目,不仅要验证内容,而且格式本身也很重要。
无论如何,希望这可以为某人节省几个小时,或者至少让他们更接近解决方案。