Python 如何将一个大的dict序列化到一个磁盘文件,这样小的修改就不会';不需要完全重写,但只需要几个字节?

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上进行如此小的修改只需要在磁盘上写入几个字节

让我们使用一个大字典(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
上进行如此小的修改只需要在磁盘上写入几个字节,要使用哪些序列化技术

当然,数据库(例如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'}