这是压缩json以存储在基于内存的存储(如redis或memcache)中的最佳方式



要求:具有2-3层嵌套的Python对象,包含基本的数据类型,如整数、字符串、列表和字典。(没有日期等),需要存储为json在redis对一个关键。为了降低内存占用,将json压缩为字符串的最佳方法是什么?目标对象不是很大,平均有1000个小元素,或转换为JSON时约15000个字符

>>> my_dict
{'details': {'1': {'age': 13, 'name': 'dhruv'}, '2': {'age': 15, 'name': 'Matt'}}, 'members': ['1', '2']}
>>> json.dumps(my_dict)
'{"details": {"1": {"age": 13, "name": "dhruv"}, "2": {"age": 15, "name": "Matt"}}, "members": ["1", "2"]}'
### SOME BASIC COMPACTION ###
>>> json.dumps(my_dict, separators=(',',':'))
'{"details":{"1":{"age":13,"name":"dhruv"},"2":{"age":15,"name":"Matt"}},"members":["1","2"]}'

1/是否有其他更好的方法来压缩json,以节省内存在redis(也确保轻量级解码之后)。

2/msgpack [http://msgpack.org/]有多好?

我们只使用gzip作为压缩器。

import gzip
import cStringIO
def decompressStringToFile(value, outputFile):
  """
  decompress the given string value (which must be valid compressed gzip
  data) and write the result in the given open file.
  """
  stream = cStringIO.StringIO(value)
  decompressor = gzip.GzipFile(fileobj=stream, mode='r')
  while True:  # until EOF
    chunk = decompressor.read(8192)
    if not chunk:
      decompressor.close()
      outputFile.close()
      return 
    outputFile.write(chunk)
def compressFileToString(inputFile):
  """
  read the given open file, compress the data and return it as string.
  """
  stream = cStringIO.StringIO()
  compressor = gzip.GzipFile(fileobj=stream, mode='w')
  while True:  # until EOF
    chunk = inputFile.read(8192)
    if not chunk:  # EOF?
      compressor.close()
      return stream.getvalue()
    compressor.write(chunk)

在我们的用例中,我们将结果存储为文件,您可以想象。要只使用内存中的字符串,您也可以使用cStringIO.StringIO()对象作为文件的替代品。

根据@Alfe上面的回答,这里是一个将内容保存在内存中的版本(用于网络I/O任务)。为了支持Python 3,我还做了一些修改。

import gzip
from io import StringIO, BytesIO
def decompressBytesToString(inputBytes):
  """
  decompress the given byte array (which must be valid 
  compressed gzip data) and return the decoded text (utf-8).
  """
  bio = BytesIO()
  stream = BytesIO(inputBytes)
  decompressor = gzip.GzipFile(fileobj=stream, mode='r')
  while True:  # until EOF
    chunk = decompressor.read(8192)
    if not chunk:
      decompressor.close()
      bio.seek(0)
      return bio.read().decode("utf-8")
    bio.write(chunk)
  return None
def compressStringToBytes(inputString):
  """
  read the given string, encode it in utf-8,
  compress the data and return it as a byte array.
  """
  bio = BytesIO()
  bio.write(inputString.encode("utf-8"))
  bio.seek(0)
  stream = BytesIO()
  compressor = gzip.GzipFile(fileobj=stream, mode='w')
  while True:  # until EOF
    chunk = bio.read(8192)
    if not chunk:  # EOF?
      compressor.close()
      return stream.getvalue()
    compressor.write(chunk)

测试压缩:

inputString="asdf" * 1000
len(inputString)
len(compressStringToBytes(inputString))
decompressBytesToString(compressStringToBytes(inputString))

我对不同的二进制格式(MessagePack、BSON、Ion、Smile cor)和压缩算法(Brotli、Gzip、XZ、Zstandard、bzip2)进行了广泛的比较。

对于我用于测试的JSON数据,保持数据为JSON并使用Brotli压缩是最好的解决方案。Brotli具有不同的压缩级别,因此如果要长时间保存数据,那么使用高级别压缩是值得的。如果您不会持续很长时间,那么较低级别的压缩或使用Zstandard可能是最有效的。

Gzip很简单,但几乎肯定会有更快的替代方案,或者提供更好的压缩,或者两者兼而有之。

你可以在这里阅读我们调查的全部细节:博客文章

如果您想要快速,请尝试lz4。如果你想让它压缩得更好,选择lzma。

是否有其他更好的方法来压缩json以节省内存Redis(也确保轻量级解码之后)?

msgpack [http://msgpack.org/]有多好?

Msgpack相对较快,占用内存较小。但对我来说,ujson通常更快。你应该在你的数据上比较它们,测量压缩和解压缩率和压缩比。

我是否也要考虑泡菜之类的选择?

同时考虑pickle(特别是pickle)和marshal。他们跑得很快。但请记住,它们不是安全的或可扩展的,您为速度付出了额外的责任。

一种简单的"后处理"方法是构建一个"短键名"映射,并在存储之前运行生成的json,然后在反序列化到对象之前再次运行(反向)。例如:

Before: {"details":{"1":{"age":13,"name":"dhruv"},"2":{"age":15,"name":"Matt"}},"members":["1","2"]}
Map: details:d, age:a, name:n, members:m
Result: {"d":{"1":{"a":13,"n":"dhruv"},"2":{"a":15,"n":"Matt"}},"m":["1","2"]}

只要遍历json并替换key->value到数据库的路径,value->key到应用程序的路径。

您也可以gzip以获得额外的好处(但之后不会是字符串)。

另一种可能性是使用MongoDB的存储格式BSON。

您可以在该站点的实现页面中找到两个python实现。

编辑:为什么不直接保存字典,并在检索时转换为json ?

最新更新