lrucache.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. class LruCache(object):
  16. """Least-recently-used cache."""
  17. # TODO(mjark) Add hit/miss counters
  18. # TODO(mjark) Add mutex for linked list for thread safety.
  19. def __init__(self, max_size):
  20. cache = {}
  21. list_root = []
  22. list_root[:] = [list_root, list_root, None, None]
  23. PREV, NEXT, KEY, VALUE = 0, 1, 2, 3
  24. def add_node(key, value):
  25. prev_node = list_root
  26. next_node = prev_node[NEXT]
  27. node = [prev_node, next_node, key, value]
  28. prev_node[NEXT] = node
  29. next_node[PREV] = node
  30. cache[key] = node
  31. def move_node_to_front(node):
  32. prev_node = node[PREV]
  33. next_node = node[NEXT]
  34. prev_node[NEXT] = next_node
  35. next_node[PREV] = prev_node
  36. prev_node = list_root
  37. next_node = prev_node[NEXT]
  38. node[PREV] = prev_node
  39. node[NEXT] = next_node
  40. prev_node[NEXT] = node
  41. next_node[PREV] = node
  42. def delete_node(node):
  43. prev_node = node[PREV]
  44. next_node = node[NEXT]
  45. prev_node[NEXT] = next_node
  46. next_node[PREV] = prev_node
  47. cache.pop(node[KEY], None)
  48. def cache_get(key, default=None):
  49. node = cache.get(key, None)
  50. if node is not None:
  51. move_node_to_front(node)
  52. return node[VALUE]
  53. else:
  54. return default
  55. def cache_set(key, value):
  56. node = cache.get(key, None)
  57. if node is not None:
  58. move_node_to_front(node)
  59. node[VALUE] = value
  60. else:
  61. add_node(key, value)
  62. if len(cache) > max_size:
  63. delete_node(list_root[PREV])
  64. def cache_set_default(key, value):
  65. node = cache.get(key, None)
  66. if node is not None:
  67. return node[VALUE]
  68. else:
  69. add_node(key, value)
  70. if len(cache) > max_size:
  71. delete_node(list_root[PREV])
  72. return value
  73. def cache_pop(key, default=None):
  74. node = cache.get(key, None)
  75. if node:
  76. delete_node(node)
  77. return node[VALUE]
  78. else:
  79. return default
  80. def cache_len():
  81. return len(cache)
  82. self.sentinel = object()
  83. self.get = cache_get
  84. self.set = cache_set
  85. self.setdefault = cache_set_default
  86. self.pop = cache_pop
  87. self.len = cache_len
  88. def __getitem__(self, key):
  89. result = self.get(key, self.sentinel)
  90. if result is self.sentinel:
  91. raise KeyError()
  92. else:
  93. return result
  94. def __setitem__(self, key, value):
  95. self.set(key, value)
  96. def __delitem__(self, key, value):
  97. result = self.pop(key, self.sentinel)
  98. if result is self.sentinel:
  99. raise KeyError()
  100. def __len__(self):
  101. return self.len()