如何设计最近最新使用的缓存?

时间:2011-11-29 19:34:21

标签: c++ algorithm data-structures hash lru

如何设计最近最新使用的缓存?

假设您访问过某些项目。您需要设计一个数据结构来保存 这些项目。每个项目都与最近访问的时间相关联。

每次访问某个项目时,请在数据结构中进行检查。如果该项目已在缓存中,请更新其访问时间。否则,将其插入缓存中。高速缓存大小是固定的,如果已满,则删除最旧的项目。

我的解决方案:

  1. 使用地图< item,visitTime>

  2. 初始化:使用f(visitTime)按降序对地图进行排序。 O(nlg n)

  3. 如果访问了某个项目,请使用O(lg n)在地图中搜索该项目。

  4. 如果已在地图中,请更新时间O(1)。对地图O进行排序(lg N)。

  5. 如果没有,请将其插入地图然后排序。 O(lg n)

  6. 如果地图尺寸>固定大小,删除最后一个元素O(1)。

  7. 另一种解决方案:

    1. 使用哈希表< item,visitTime>

    2. 将其排序为O(n lgn)。

    3. 如果访问了某个项目,请使用O(1)在该项目中进行搜索。

    4. 如果已在表中,请更新时间O(1)。对表格进行排序 O(n lg n)。

    5. 如果没有,请将其插入表格然后排序。 O(n lg n)

    6. 如果表格大小>固定大小,删除最后一个元素O(1)。

    7. 有更好的解决方案吗?上) ?

6 个答案:

答案 0 :(得分:4)

如果您使用双向链接列表,您将获得O(1)插入(搜索后),O(1)删除,O(n)搜索。

假设您在前面插入新项目:

如果缓存未满,只需添加到前面(O(1))。

如果您需要更新项目,找到它(O(n)),将其从链接列表中删除(O(1)),然后添加到前面(O(1))。

如果您需要删除最旧的项目以插入新项目,请删除结束元素(O(1)),然后插入前面(O(1))[注意:您需要先在此列表中搜索如果该项目尚未在缓存中,则为O(n)]。

链接列表也可以同时给你,因为搜索会让你离开最后一个元素。

答案 1 :(得分:3)

Python's LRU cache有O(1)插入,删除和搜索。它的设计使用一个双向链接的条目列表(排列最旧到最新)和一个哈希表来定位特定的链接。

这是一个简化(但很快)的版本,在40行非常基本的Python中。将Python的解决方案转换为C ++应该不难:

class LRU_Cache(object):

    def __init__(self, original_function, maxsize=1000):
        self.original_function = original_function
        self.maxsize = maxsize
        self.mapping = {}

        PREV, NEXT, KEY, VALUE = 0, 1, 2, 3
        self.head = [None, None, None, None]        # oldest
        self.tail = [self.head, None, None, None]   # newest
        self.head[NEXT] = self.tail

    def __call__(self, *key):
        PREV, NEXT, KEY, VALUE = 0, 1, 2, 3
        mapping, head, tail = self.mapping, self.head, self.tail
        sentinel = object()

        link = mapping.get(key, sentinel)
        if link is sentinel:
            value = self.original_function(*key)
            if len(mapping) >= self.maxsize:
                oldest = head[NEXT]
                next_oldest = oldest[NEXT]
                head[NEXT] = next_oldest
                next_oldest[PREV] = head
                del mapping[oldest[KEY]]
            last = tail[PREV]
            link = [last, tail, key, value]
            mapping[key] = last[NEXT] = tail[PREV] = link
        else:
            link_prev, link_next, key, value = link
            link_prev[NEXT] = link_next
            link_next[PREV] = link_prev
            last = tail[PREV]
            last[NEXT] = tail[PREV] = link
            link[PREV] = last
            link[NEXT] = tail
        return value

if __name__ == '__main__':
    p = LRU_Cache(ord, maxsize=3)
    for c in 'abcdecaeaa':
        print(c, p(c))

答案 2 :(得分:1)

使用两个共享相同数据的集合。有一个哈希表和一个列表。使用哈希表来验证项是否存在,并在列表中找到它(哈希映射的值是列表迭代器)。使用列表维护项目之间的顺序。同步两个集合(从列表中删除项目时,从哈希表中删除相应的项目)。列表迭代器必须是这样的,当你在列表中重新定位项时它不会改变。

编辑:std :: list iterator在添加和删除元素时有效,前提是迭代器引用的元素不会被删除。请参阅维基百科中Capacity and Allocation部分的最后几行。

答案 3 :(得分:1)

您可以使用java.util.LinkedHashSet在Java中执行此操作。它是一个与链表相结合的哈希表,它保留了项目的插入顺序。如果密钥扩散效果良好,您应该(预期)获得恒定的时间查找,插入和删除。

您可能还想查看实现自动机制的WeakHashMap,其中元素可以被垃圾收集。

答案 4 :(得分:0)

您不必对容器进行排序。只需将项目添加到地图或矢量中,然后线性地查看它以查找所需项目(或最旧的项目)。

然后它将是O(n)

答案 5 :(得分:0)

看看boost::multi_index。它显示的一个示例是MRU List