跳转到主要内容

基于BTree的ZODB友好的列表实现

项目描述

概述

这个包中的序列具有类似列表的API,但将其值存储在单独的桶中。这意味着对于大型序列中的小变化,序列可能会带来很大的优势。例如,一个基于有序BTree的容器可能希望在序列中存储顺序,这样移动操作只会在数据库中重写一个或两个桶(大约50个字符串或更少),而不是整个内容(例如,可能是数千个字符串)。

如果序列通常是完全重新排列的,那么这个包中的代码的复杂性是不理想的。只有在更改通常相当小的情况下才合理。

一个缺点是读取和写入比普通列表要复杂。如果这实际上要取得进展,可能将其中一些或全部用C编写会有所帮助。然而,它仍然看起来相当快。

另一个缺点是上述桶优势的必然结果:对于更多持久化对象,遍历它将填满大量的ZODB对象缓存(该缓存基于缓存的物体数量,而不是大小)。如果你使用这些存储大量数据并且频繁遍历或更改,请考虑指定一个大的对象缓存。

这些序列返回切片作为迭代器,并添加一些有用的迭代方法。它添加了一个复制方法,提供了blist的廉价复制,共享所有桶和索引,直到发生写入操作,此时它复制并修改受影响的索引和桶。

我们将简要了解这些差异如何工作,然后描述实现的基本机制,最后简要讨论性能特性。

与Python列表的不同之处

切片是迭代器

这不需要太多讨论。获取各种切片会返回迭代器。

>>> from zc.blist import BList
>>> l = BList(range(1000))
>>> l[345:351] # doctest: +ELLIPSIS
<generator object at ...>
>>> list(l[345:351])
[345, 346, 347, 348, 349, 350]
>>> l[351:345:-1] # doctest: +ELLIPSIS
<generator object at ...>
>>> list(l[351:345:-1])
[351, 350, 349, 348, 347, 346]
>>> l[345:351:2] # doctest: +ELLIPSIS
<generator object at ...>
>>> list(l[345:351:2])
[345, 347, 349]

额外的迭代方法

iterReversed 允许您以给定的起始点高效地反向遍历列表。它用于步长为 -1 的切片。

>>> i = l.iterReversed()
>>> i.next()
999
>>> i.next()
998
>>> list(i)[-10:]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

iterSlice 允许您使用切片遍历列表。它相当于使用切片调用 __getitem__。

>>> i = l.iterSlice(345, 351, 2)
>>> i # doctest: +ELLIPSIS
<generator object at ...>
>>> list(i)
[345, 347, 349]

便宜的复制

copy 方法生成给定 blist 的廉价副本。所有桶和索引在任一侧发生变化之前都是共享的。可以安全地复制其他副本。

>>> c = l.copy()
>>> l == c
True
>>> list(c) == list(l)
True
>>> del c[10:]
>>> list(l) == range(1000)
True
>>> list(c)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l == c
False
>>> c2 = c.copy()
>>> c2 == c
True
>>> list(c2)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

机制

在其实现中,序列是一个改进的 B+ 树。索引是键,但每个桶或分支从 0 开始。例如,一个完全平衡的包含 16 个元素的桶序列,桶或分支中最多有 3 个条目,其“键”如下。在图中,前三行是索引,最后一行是桶。

     0           8
  0     4     0     4
 0 2   0 2   0 2   0 2
01 01 01 01 01 01 01 01

例如,您会通过以下过程获得位置 5 的值

  • 在顶层索引(第一行,键为 0 和 8)中,找到小于所需位置的最大键,并使用关联的值(索引或桶,在这种情况下是第二行中由第一对 0 和 4 表示的索引)作为下一步。在这种情况下,顶层索引的键为 0 和 8,因此小于位置 5 的最大键是 0。从位置中减去此键以进行下一步。这个差值将用作下一步的位置。在这种情况下,下一步的位置将是(5-0=)5。

  • 下一个索引具有键 0 和 4。小于 5 的最大键是 4。使用与 4 键关联的子索引进行下一步(第三行中的第二对 0 和 2),并从位置(5)减去键(4)以确定下一步要使用的位置(=1)。

  • 下一个索引(第三行中的第二对 0 和 2)需要找到位置 1。这将返回最后一行中的第三对 0 1。新位置将是(1-0=)1。

  • 最后,底部桶中的位置 1 存储了实际所需的值。

这种安排最小化了在序列中插入新值时对键的更改:忽略树的平衡,只需调整父项及其后续兄弟项。例如,在上述桶序列的 0 位置插入新值(从算法的角度来看,这是最坏的情况,涉及到的对象数量最多)会导致以下树

     0           9
  0     5     0     4
 0  3   0 2   0 2   0 2
012 01 01 01 01 01 01 01

性能特征

优点

  • __getitem__ 是高效的,不会加载不必要的桶。它处理切片也相当不错,即使切片非常大,也不会加载中间桶。切片目前返回可迭代对象而不是列表;这可能会切换到某种视图。现在应该假定的是,您可以遍历切片的结果。

  • __setitem__ 和所有写方法在高效加载桶和仅写入所需内容方面做得相当不错,并支持完整的 Python 切片语义。

  • copy 是廉价的:它重用桶和索引,以便在它们发生变化时懒惰地创建新的内部组件。

  • 虽然 __contains____iter__index 和其他方法是暴力方法并使用 Python 编写,但它们可能不会加载所有桶和项目,而使用常规列表或元组,它们总是会这样做。另请参阅 iteriterReversediterSlice

一般之处

  • count__eq__ 和其他方法会加载所有桶和项目,并且是暴力搜索,在 Python 中。相比之下,列表和元组会加载所有项目(更差),在 C 中是暴力搜索(较好,但不是算法上的)。

缺点

  • 这将为一个 blist 创建许多持久化对象,这可能会根据环境和使用情况导致缓存淘汰问题。

  • 我提到过这是在 Python 中,而不是 C 中吗?至少这是可修复的,实际上目前似乎没有太大问题,至少对于作者的用法来说是这样。

变更

1.0b2 (2009-03-12)

  • 修复:内部数据结构在 ZODB 中存储不正确,因此从新的数据库连接加载的 BLists 会损坏。

  • 移除了未使用的代码和对 rwproperty 的依赖。

  • 改进了 BList API 的测试覆盖率,修复了对索引 -1 或有效索引范围末端的项的访问。

1.0b1 (2008-10-06)

初始版本。

项目详情


下载文件

下载您平台上的文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。

源代码分发

zc.blist-1.0b2.tar.gz (25.0 kB 查看散列

上传时间 源代码

支持者