8

我有一个大约 36 GB 的 json 文件(来自 wikidata),我想更有效地访问它。目前我在 C++ 中使用 rapidjsons SAX 风格的 API - 但解析整个文件在我的机器上需要大约 7415200 毫秒(=120 分钟)。我想根据 json 对象内的两个主键之一(“名称”或“实体键”-> 即“堆栈溢出”或“Q549037”)访问此文件中的 json 对象。这意味着我必须在最坏的情况下解析当前的整个文件。

所以我想到了两种方法:

  • 将大文件拆分为数十亿个小文件 - 文件名指示名称/实体键(即 Q549037.json / Stack_Overflow.json 或 Q549037#Stack_Overflow.json) -> 不确定存储中的过载
  • ftell()从主键到文件中的位置建立某种索引。建立索引大约需要 120 分钟(就像现在解析一样),但访问应该更快
    • 即使用类似两个的东西std::unorderedmap(可能会再次遇到内存问题)
    • 索引文件 - 创建两个文件:一个具有按名称排序的条目,另一个按实体键排序(创建这些文件可能需要更长的时间,因为排序)

像这样的问题的最佳实践是什么?我应该遵循哪种方法?还有其他想法吗?

4

4 回答 4

3

我认为性能问题不是由于解析造成的。使用 RapidJSON 的 SAX API 应该已经提供了良好的性能和内存友好。如果您需要访问 JSON 中的每个值,这可能已经是最佳解决方案。

但是,从问题描述来看,一次读取所有值似乎不是您的要求。您想要读取特定标准的一些(可能是少量)值(例如,通过主键)。然后读取/解析所有内容不适合这种情况。

您将需要一些索引机制。可以使用文件位置执行此操作。如果这些位置的数据也是有效的 JSON,您可以查找并将其流式传输到 RapidJSON 以解析该 JSON 值(当解析完整的 JSON 时,RapidJSON 可以停止解析,by kParseStopWhenDoneFlag)。

其他选项是将 JSON 转换为某种数据库,SQL 数据库、键值数据库或自定义数据库。使用提供的索引工具,您可以快速查询数据。这可能需要很长时间进行转换,但对于以后的检索来说性能很好。

请注意,JSON 是一种交换格式。它不是为对大数据进行快速个人查询而设计的。


更新:最近我发现有一个项目半索引可能适合您的需求。

于 2015-02-09T05:25:18.200 回答
1

编写您自己的 JSON 解析器,最大限度地减少分配和数据移动。也为直接的 ANSI 抛弃多字符。我曾经写过一个 XML 解析器来解析 4GB 的 Xml 文件。我试过 MSXML 和 Xerces 都有轻微的内存泄漏,当用于这么多数据时实际上会耗尽内存。一旦达到最大嵌套级别,我的解析器实际上会停止内存分配。

于 2015-02-08T19:38:34.177 回答
1

您对问题的定义不允许给出准确的答案。

我想知道为什么您首先要坚持使用 JSON。它当然不是快速访问大数据的最佳格式。

如果您大量使用 wikia 数据,为什么不将它们完全转换成更易于管理的格式呢?

自动化与您的条目格式匹配的数据库定义应该很容易,并且一劳永逸地将大量 JSON 转换为数据库记录。

您可以随时停止数据库转换(即将每个 JSON 块存储为纯文本或进一步细化)。
在最小的情况下,您最终会得到一个数据库表,其中包含按名称和键索引的记录。
肯定比将文件系统用作数据库(通过创建数百万个以名称+键命名的文件)或编写专用代码来查找记录要少得多。

这也可能会为您节省大量磁盘空间,因为内部数据库存储通常比纯文本表示更有效。

于 2015-02-13T21:53:39.977 回答
0

我已经对维基百科中的数据进行了一些解析。我对提取方程特别感兴趣,所以我只对文件的一部分感兴趣。

首先,如果您对它的 WikiMedia 数据感兴趣,那么获得实验室帐户会容易得多。这需要大约一天的时间,它可以让你在他们的机器上运行大部分代码,避免下载数 GB 的需要。使用 Labs 帐户,您应该能够在相当最新的数据库复制上运行代码,而无需完全使用 json。

我使用一个简单的 python 程序来解析数据,它基本上在每一行运行几个正则表达式;一个查找包含的行,<title>...</title>因此我知道它是哪篇维基百科文章,还有一些查找名称空间和数学标签。它可以在 13 秒内处理 160MB 的文件,因此可能可以在一个小时内完成整个 36GB。

此代码生成仅包含我感兴趣的数据的文本文件。如果您感兴趣,代码是

import sys
import re

dump = len(sys.argv)>1 and sys.argv[1]=='-d'
titleRE = re.compile('<title>(.*)</title>')
nsRE = re.compile('<ns>(.*)</ns>')
mathRE = re.compile('&lt;/?math(.*?)&gt;')
pageEndRE = re.compile('</page>')
supOc = 0
supCc = 0
subOc = 0
subCc = 0

title =""
attr = ""
ns = -1
inEqn = 0
for line in sys.stdin:
    m = titleRE.search(line)
    if m :
        title = m.group(1)
        expression = ""
        if dump : print line
        inEqn = 0
    m = nsRE.search(line)
    if m :
        ns = m.group(1)
    start = 0
    pos = 0
    m = mathRE.search(line,pos)
    while m :
        if m.group().startswith('&lt;math'):
            attr = m.group(1)
            start = m.end()
            pos = start
            expression = ""
            inEqn = 1
        if m.group() == '&lt;/math&gt;' :
            end = m.start()
            expression = '    '.join([expression,line[start:end]])
            print title,'\t',attr,'\t',expression.lstrip().replace('&lt;','<').replace('&gt;',
'>').replace('&amp;','&')
            pos = m.end()
            expression = ""
            start = 0
            inEqn = 0
        m = mathRE.search(line,pos)
    if start > 0 :
        expression = line[start:].rstrip()
    elif inEqn :
        expression = '    '.join([expression,line.rstrip()])

对不起,如果它有点神秘,但它不是供公众消费的。样本输出为

Arithmetic mean         a_1,\ldots,a_n.
Arithmetic mean         A
Arithmetic mean         A=\frac{1}{n}\sum_{i=1}^{n} a_i
Arithmetic mean         \bar{x}

每行都有文章的名称和乳胶方程式。这将我需要处理的数据减少到更易于管理的 500k。我不确定这样的策略是否适用于您的应用程序。

对于主要的 enwiki 数据,将 xml 转储拆分为 27 个较小的文件,大小大致相同。您可能会发现一些大小合理的文件,比一个大文件或数百万个小文件更容易处理。可能很容易按文章标题中的第一个字母进行拆分,从而给出不到一百个文件,每个文件小于 1 GB。

于 2015-02-17T01:14:45.930 回答