Python 如何将一个大的dict序列化到一个磁盘文件,这样小的修改就不会';不需要完全重写,但只需要几个字节?
让我们使用一个大字典(110 MB)并将其保存到磁盘:Python 如何将一个大的dict序列化到一个磁盘文件,这样小的修改就不会';不需要完全重写,但只需要几个字节?,python,sqlite,dictionary,serialization,diff,Python,Sqlite,Dictionary,Serialization,Diff,让我们使用一个大字典(110 MB)并将其保存到磁盘: import pickle, os d = {i: os.urandom(100) for i in range(1_000_000)} with open('mydict', 'wb') as f: pickle.dump(d, f) # 110 MB file 现在让我们做一些小的修改(在100万个关键字/值中仅更改了2个): 如何避免重写整条记录? 在d上进行如此小的修改只需要在磁盘上写入几个字节
import pickle, os
d = {i: os.urandom(100) for i in range(1_000_000)}
with open('mydict', 'wb') as f:
pickle.dump(d, f) # 110 MB file
现在让我们做一些小的修改(在100万个关键字/值中仅更改了2个):
如何避免重写整条记录?
在d
上进行如此小的修改只需要在磁盘上写入几个字节,要使用哪些序列化技术
当然,数据库(例如Sqlite)可能是一个解决方案,但我想先看看是否有更简单的技术
类似这样的代码(伪代码):
还是有其他更适合这种情况的数据结构
编辑:正如评论中所建议的,我尝试了
shelve
:
import shelve
d = shelve.open("dict2")
for i in range(1_000_000):
d[str(i)] = os.urandom(100)
但这需要300秒和514 MB!(对于writeback=True,情况相同)
作为比较,与上面的第一个代码(修改为使用str(i)
而不是i
作为键进行公平比较)相比,它只需要4秒和120 MB
因此,shelve
在这里似乎没有很好地适应
另外,shelve
基本上只是与dict值的pickle
一起使用。因此,值得直接查看dbm
。不幸的是,在Windows上,只有可用的,而且有点弱:,“目前,被删除或扩展的项目占用的空间永远不会被重用。”如果您想要的是非常快速的写入,您可以在最后使用常规文本模式将其附加到YAML文件中。最后一个关键值“赢”
因此,您的写入速度基本上与每次追加一行相同。由于YAML,读取速度会很慢,但您可以始终在YAML模式下读取/写入以去除重复数据
注意:YAML,而不是JSON,因为JSON需要在文件结尾加上“}或
]``,这会使事情复杂化
test.py
输出:
整数键工作正常。我通过将{4:4}
添加到种子值进行测试。二进制格式通常非常复杂,因此它们不容易“修补”到位。在大多数情况下,实现这样的功能会比它的价值更麻烦。如果您只是想减少磁盘写入量,您可以计算一个差异字典,并将其作为一个单独的文件写入磁盘?似乎您需要一个,另请参见此(和答案)。@Basj更新了注释:)@DaniMesejo您是否可以从问题中获得我的示例的示例代码,显示有托架,在第二次序列化过程中只写入了几个字节?@Basj我可以指出这一点,它清楚地表明搁置只写入更改。我想不出一个例子来证明这一点。谢谢这个好的解决方案!几天前,我终于使用了一种非常类似的方法,其原理是“您只需将其附加到YAML文件中,最后使用常规文本模式。最后一个键值“wins”。“它确实工作得很好!”!
before_modification_state = d.getstate()
d[17] = "hello" # addition
del d[1234] # deletion
diff = d.getdiff(from=before_modification_state) # only a few bytes
patch('mydict', diff) # this only writes a few bytes to the disk file "mydict"
# or
with open('mydict', 'r+') as f: # read-write
for pos, newbytes in diff:
f.seek(pos) # move to position pos
f.write(newbytes) # write the new bytes
# with this solution d.getdiff() would return something like
# [[6576, b"fsq678"], [16537, b"!/=13IH"]]
# i.e. position to seek in file, and new bytes to write
import shelve
d = shelve.open("dict2")
for i in range(1_000_000):
d[str(i)] = os.urandom(100)
from yaml import safe_load as yload, safe_dump as ydump
import sys
from pathlib import Path
pa_yaml = Path(__file__).with_suffix(".yaml")
value = sys.argv[1]
if pa_yaml.exists():
existed = True
with pa_yaml.open("a") as fo:
fo.write(f"value: {value}\n")
else:
existed = False
data = dict(dummy="dummy", value=value)
print("seeding test.yaml with {data=}")
with pa_yaml.open("w") as fo:
fo.write(ydump(data))
print(f"\n{pa_yaml} contents after write:\n{pa_yaml.read_text()}")
with pa_yaml.open() as fi:
data = yload(fi)
print(f"after write {data=}")
(venv38) me@test_206_yaml$ python test.py value1
seeding test.yaml with {data=}
test.yaml contents after write:
dummy: dummy
value: value1
after write data={'dummy': 'dummy', 'value': 'value1'}
(venv38) me@test_206_yaml$ python test.py value2
test.yaml contents after write:
dummy: dummy
value: value1
value: value2
after write data={'dummy': 'dummy', 'value': 'value2'}
(venv38) me@test_206_yaml$ python test.py value3
test.yaml contents after write:
dummy: dummy
value: value1
value: value2
value: value3
after write data={'dummy': 'dummy', 'value': 'value3'}