【问题标题】:Is there a memory efficient and fast way to load big JSON files?是否有一种内存高效且快速的方式来加载大型 JSON 文件?
【发布时间】:2011-01-24 22:21:27
【问题描述】:

我有一些 500MB 的 json 文件。 如果我使用“琐碎的”json.load() 一次性加载所有内容,会消耗大量内存。

有没有办法部分读取文件?如果它是一个以行分隔的文本文件,我将能够遍历这些行。我正在寻找它的类比。

【问题讨论】:

  • 我面临的问题是我有 195 个这样的文件要处理,而且似乎 python 的垃圾收集器做得不好。在第 10 个文件之后,我的内存不足。我在 Windows 7 上使用 Python 2.6.4。我有 3GB 内存
  • 为什么需要一次将它们全部加载到内存中?这似乎无效。
  • 我不必一次加载所有这些,但垃圾收集器似乎工作不正常。关闭许多文件后,它会消耗大量内存。当我遍历文件时,json 对象总是具有相同的变量名,并且我假设垃圾收集器应该释放其他文件占用的内存。但这只是没有发生
  • @user210481:“假设垃圾收集器应该释放内存”它应该。既然没有,那就有别的问题了。
  • @Jim Pivarski 的回答应该是被接受的。

标签: python json large-files


【解决方案1】:

更新

请参阅其他答案以获取建议。

2010 年的原始答案,现已过时

简短回答:不。

正确分割 json 文件需要对 json 对象图有深入的了解才能做到正确。

但是,如果你有这方面的知识,那么你可以实现一个类似文件的对象来包装 json 文件并吐出适当的块。

例如,如果你知道你的 json 文件是一个对象数组,你可以创建一个生成器来包装 json 文件并返回数组的块。

您必须进行一些字符串内容解析才能正确分块 json 文件。

我不知道是什么生成了您的 json 内容。如果可能的话,我会考虑生成一些可管理的文件,而不是一个大文件。

【讨论】:

  • 很遗憾,我无法在此处发布文件,而且它也不是由我生成的。我正在考虑使用常规 json.load 读取 json 文件并生成一个新的文本行分隔文件以对其进行迭代。我面临的问题是我有 195 个这样的文件要处理,而且似乎 python 的垃圾收集器做得不好。在第 10 个文件之后,我的内存不足。我在 Windows 7 上使用 Python 2.6.4。
  • 如果有一个用于 Python 的类似 SAX 的 JSON api,那就太酷了。就像 Java 的 JACKSON。
  • 不幸的是,这个答案已被接受,因为有现有的和工作的 Python 增量 json 解析器......
  • 我试图删除答案,但这不适用于已接受的答案。将编辑。
  • @brunodesthuilliers 当 json 是 index 格式的一个巨大字符串时,你有增量解析的建议吗?见我的question
【解决方案2】:

关于您提到的内存不足,我必须质疑您是否真的在管理内存。在尝试读取新对象之前,您是否使用“del”关键字删除旧对象?如果你删除它,Python 永远不应该在内存中默默地保留它。

【讨论】:

  • 我没有使用 del 命令,因为我认为它是自动执行的,因为没有更多对它的引用。
  • 由于它没有被删除,你仍然有参考。全局变量是常见的问题。
【解决方案3】:

除了@codeape

我会尝试编写一个自定义 json 解析器来帮助您找出正在处理的 JSON blob 的结构。只打印出键名,等等。制作一个分层树并(自己)决定如何分块。这样你就可以按照@codeape 的建议去做——把文件分成更小的块,等等

【讨论】:

    【解决方案4】:

    所以问题不在于每个文件太大,而在于它们太多,而且它们似乎在内存中加起来。 Python 的垃圾收集器应该没问题,除非您保留不需要的引用。如果没有任何进一步的信息,很难准确判断发生了什么,但您可以尝试一些事情:

    1. 模块化您的代码。执行以下操作:

      for json_file in list_of_files:
          process_file(json_file)
      

      如果您以不依赖任何全局状态的方式编写 process_file(),并且不 改变任何全局状态,垃圾收集器应该能够完成它的工作。

    2. 在单独的进程中处理每个文件。不要一次解析所有 JSON 文件,而是编写一个 只解析一个,并从 shell 脚本或另一个 python 传递每个 通过subprocess.Popen 调用您的脚本的进程。这有点不太优雅,但如果 没有其他方法,它将确保您不会将陈旧数据从一个文件保存到 下一个。

    希望这会有所帮助。

    【讨论】:

      【解决方案5】:

      另一个想法是尝试将其加载到像 MongoDB 这样的文档存储数据库中。 它可以很好地处理大量的 JSON。虽然您在加载 JSON 时可能会遇到同样的问题 - 通过一次加载一个文件来避免该问题。

      如果路径适合您,那么您可以通过他们的客户端与 JSON 数据进行交互,并且可能不必将整个 blob 保存在内存中

      http://www.mongodb.org/

      【讨论】:

        【解决方案6】:

        “垃圾收集器应该释放内存”

        正确。

        既然没有,那就有别的问题了。一般来说,内存无限增长的问题是全局变量。

        删除所有全局变量。

        将所有模块级代码变成更小的函数。

        【讨论】:

        • 这没有帮助,而且离题了。
        【解决方案7】:

        这个问题有一个副本有更好的答案。见https://stackoverflow.com/a/10382359/1623645,它建议ijson

        更新:

        我试过了,ijson 之于 JSON 就像 SAX 之于 XML。例如,您可以这样做:

        import ijson
        for prefix, the_type, value in ijson.parse(open(json_file_name)):
            print prefix, the_type, value
        

        prefix 是 JSON 树中的点分隔索引(如果您的键名中有点会发生什么?我想这对 Javascript 也不利……),theType 描述了 SAX -like 事件,'null', 'boolean', 'number', 'string', 'map_key', 'start_map', 'end_map', 'start_array', 'end_array'value 之一是对象的值,如果 the_type 是像开始/结束地图/数组这样的事件,则 None

        该项目有一些文档字符串,但没有足够的全局文档。我必须深入了解ijson/common.py 才能找到我想要的东西。

        【讨论】:

        • 我发现这不仅是对这个问题的最佳回答,而且是我在谷歌搜索后找到的最有用的 ijson 介绍。感谢您抽出宝贵时间浏览稀疏文档并如此简单明了地展示其基本功能。
        • 不错的链接。还有另一个 ijson 功能 - 生成器在 JSON 数据的给定位置生成字典。将执行时间与其他解决方案进行比较,ijson 相当慢(57 秒与 stdlib json 相比),但如果您需要保持较低的内存消耗(13 MB 与 stdlib json 439 MB),则非常好。使用 yajl2 后端,它并没有更快,但内存消耗下降到 5 MB。测试了 3 个文件,每个文件大约 30 MB 大,有 30 万条记录。
        【解决方案8】:

        是的。

        您可以使用我编写的 jsonstreamer 类似 SAX 的推送解析器,它允许您解析任意大小的块,您可以 get it here 并查看自述文件以获取示例。它的速度很快,因为它使用了 'C' yajl 库。

        【讨论】:

          【解决方案9】:

          可以使用ijson 来完成。 Jim Pivarski 在上面的答案中很好地解释了 ijson 的工作。下面的代码将读取一个文件并打印列表中的每个 json。例如文件内容如下

          [{"name": "rantidine",  "drug": {"type": "tablet", "content_type": "solid"}},
          {"name": "nicip",  "drug": {"type": "capsule", "content_type": "solid"}}]
          

          您可以使用以下方法打印数组的每个元素

           def extract_json(filename):
                with open(filename, 'rb') as input_file:
                    jsonobj = ijson.items(input_file, 'item')
                    jsons = (o for o in jsonobj)
                    for j in jsons:
                       print(j)
          

          注意:'item'是ijson给出的默认前缀。

          如果您只想根据条件访问特定的 json,您可以通过以下方式进行。

          def extract_tabtype(filename):
              with open(filename, 'rb') as input_file:
                  objects = ijson.items(input_file, 'item.drugs')
                  tabtype = (o for o in objects if o['type'] == 'tablet')
                  for prop in tabtype:
                      print(prop)
          

          这将只打印类型为平板电脑的 json。

          【讨论】:

            【解决方案10】:

            您可以将JSON文件解析为CSV文件,并可以逐行解析:

            import ijson
            import csv
            
            
            def convert_json(self, file_path):
                did_write_headers = False
                headers = []
                row = []
            
                iterable_json = ijson.parse(open(file_path, 'r'))
            
                with open(file_path + '.csv', 'w') as csv_file:
                    csv_writer = csv.writer(csv_file, ',', '"', csv.QUOTE_MINIMAL)
            
                    for prefix, event, value in iterable_json:
                        if event == 'end_map':
                            if not did_write_headers:
                                csv_writer.writerow(headers)
                            did_write_headers = True
                            csv_writer.writerow(row)
                            row = []
                        if event == 'map_key' and not did_write_headers:
                            headers.append(value)
                        if event == 'string':
                            row.append(value)
            

            【讨论】:

              【解决方案11】:

              所以简单地使用 json.load() 会花费很多时间。相反,您可以使用键值对将 json 数据逐行加载到字典中,并将该字典附加到最终字典中,然后将其转换为 pandas DataFrame,这将有助于您进一步分析

              def get_data():
                  with open('Your_json_file_name', 'r') as f:
                      for line in f:
                          yield line
              
              
              data = get_data()
              data_dict = {}
              each = {}
              
              
              for line in data:
                  each = {}
                   # k and v are the key and value pair 
                  for k, v in json.loads(line).items():
                      #print(f'{k}: {v}')
                      each[f'{k}'] = f'{v}' 
                  data_dict[i] = each
              Data = pd.DataFrame(data_dict)
              #Data will give you the dictionary data in dataFrame (table format) but it will 
               #be in transposed form , so will then finally transpose the dataframe as ->
              Data_1 = Data.T 
              

              【讨论】:

                猜你喜欢
                • 2017-02-18
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-07-02
                • 2020-10-10
                • 2014-03-28
                • 2019-06-17
                相关资源
                最近更新 更多