lru.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. function hOP(obj, key) {
  2. return Object.prototype.hasOwnProperty.call(obj, key)
  3. }
  4. function naiveLength() {
  5. return 1
  6. }
  7. export default function LRUCache(options) {
  8. if (!(this instanceof LRUCache))
  9. return new LRUCache(options)
  10. if (typeof options === 'number')
  11. options = {max: options}
  12. if (!options)
  13. options = {}
  14. this._max = options.max
  15. // Kind of weird to have a default max of Infinity, but oh well.
  16. if (!this._max || !(typeof this._max === "number") || this._max <= 0)
  17. this._max = Infinity
  18. this._lengthCalculator = options.length || naiveLength
  19. if (typeof this._lengthCalculator !== "function")
  20. this._lengthCalculator = naiveLength
  21. this._allowStale = options.stale || false
  22. this._maxAge = options.maxAge || null
  23. this._dispose = options.dispose
  24. this.reset()
  25. }
  26. // resize the cache when the max changes.
  27. Object.defineProperty(LRUCache.prototype, "max",
  28. {
  29. set: function (mL) {
  30. if (!mL || !(typeof mL === "number") || mL <= 0) mL = Infinity
  31. this._max = mL
  32. if (this._length > this._max) trim(this)
  33. }
  34. , get: function () {
  35. return this._max
  36. }
  37. , enumerable: true
  38. })
  39. // resize the cache when the lengthCalculator changes.
  40. Object.defineProperty(LRUCache.prototype, "lengthCalculator",
  41. {
  42. set: function (lC) {
  43. if (typeof lC !== "function") {
  44. this._lengthCalculator = naiveLength
  45. this._length = this._itemCount
  46. for (var key in this._cache) {
  47. this._cache[key].length = 1
  48. }
  49. } else {
  50. this._lengthCalculator = lC
  51. this._length = 0
  52. for (var key in this._cache) {
  53. this._cache[key].length = this._lengthCalculator(this._cache[key].value)
  54. this._length += this._cache[key].length
  55. }
  56. }
  57. if (this._length > this._max) trim(this)
  58. }
  59. , get: function () {
  60. return this._lengthCalculator
  61. }
  62. , enumerable: true
  63. })
  64. Object.defineProperty(LRUCache.prototype, "length",
  65. {
  66. get: function () {
  67. return this._length
  68. }
  69. , enumerable: true
  70. })
  71. Object.defineProperty(LRUCache.prototype, "itemCount",
  72. {
  73. get: function () {
  74. return this._itemCount
  75. }
  76. , enumerable: true
  77. })
  78. LRUCache.prototype.forEach = function (fn, thisp) {
  79. thisp = thisp || this
  80. var i = 0
  81. var itemCount = this._itemCount
  82. for (var k = this._mru - 1; k >= 0 && i < itemCount; k--) if (this._lruList[k]) {
  83. i++
  84. var hit = this._lruList[k]
  85. if (isStale(this, hit)) {
  86. del(this, hit)
  87. if (!this._allowStale) hit = undefined
  88. }
  89. if (hit) {
  90. fn.call(thisp, hit.value, hit.key, this)
  91. }
  92. }
  93. }
  94. LRUCache.prototype.keys = function () {
  95. var keys = new Array(this._itemCount)
  96. var i = 0
  97. for (var k = this._mru - 1; k >= 0 && i < this._itemCount; k--) if (this._lruList[k]) {
  98. var hit = this._lruList[k]
  99. keys[i++] = hit.key
  100. }
  101. return keys
  102. }
  103. LRUCache.prototype.values = function () {
  104. var values = new Array(this._itemCount)
  105. var i = 0
  106. for (var k = this._mru - 1; k >= 0 && i < this._itemCount; k--) if (this._lruList[k]) {
  107. var hit = this._lruList[k]
  108. values[i++] = hit.value
  109. }
  110. return values
  111. }
  112. LRUCache.prototype.reset = function () {
  113. if (this._dispose && this._cache) {
  114. for (var k in this._cache) {
  115. this._dispose(k, this._cache[k].value)
  116. }
  117. }
  118. this._cache = Object.create(null) // hash of items by key
  119. this._lruList = Object.create(null) // list of items in order of use recency
  120. this._mru = 0 // most recently used
  121. this._lru = 0 // least recently used
  122. this._length = 0 // number of items in the list
  123. this._itemCount = 0
  124. }
  125. // Provided for debugging/dev purposes only. No promises whatsoever that
  126. // this API stays stable.
  127. LRUCache.prototype.dump = function () {
  128. return this._cache
  129. }
  130. LRUCache.prototype.dumpLru = function () {
  131. return this._lruList
  132. }
  133. LRUCache.prototype.set = function (key, value, maxAge) {
  134. maxAge = maxAge || this._maxAge
  135. var now = maxAge ? Date.now() : 0
  136. if (hOP(this._cache, key)) {
  137. // dispose of the old one before overwriting
  138. if (this._dispose)
  139. this._dispose(key, this._cache[key].value)
  140. this._cache[key].now = now
  141. this._cache[key].maxAge = maxAge
  142. this._cache[key].value = value
  143. this.get(key)
  144. return true
  145. }
  146. var len = this._lengthCalculator(value)
  147. var hit = new Entry(key, value, this._mru++, len, now, maxAge)
  148. // oversized objects fall out of cache automatically.
  149. if (hit.length > this._max) {
  150. if (this._dispose) this._dispose(key, value)
  151. return false
  152. }
  153. this._length += hit.length
  154. this._lruList[hit.lu] = this._cache[key] = hit
  155. this._itemCount++
  156. if (this._length > this._max)
  157. trim(this)
  158. return true
  159. }
  160. LRUCache.prototype.has = function (key) {
  161. if (!hOP(this._cache, key)) return false
  162. var hit = this._cache[key]
  163. if (isStale(this, hit)) {
  164. return false
  165. }
  166. return true
  167. }
  168. LRUCache.prototype.get = function (key) {
  169. return get(this, key, true)
  170. }
  171. LRUCache.prototype.peek = function (key) {
  172. return get(this, key, false)
  173. }
  174. LRUCache.prototype.pop = function () {
  175. var hit = this._lruList[this._lru]
  176. del(this, hit)
  177. return hit || null
  178. }
  179. LRUCache.prototype.del = function (key) {
  180. del(this, this._cache[key])
  181. }
  182. function get(self, key, doUse) {
  183. var hit = self._cache[key]
  184. if (hit) {
  185. if (isStale(self, hit)) {
  186. del(self, hit)
  187. if (!self._allowStale) hit = undefined
  188. } else {
  189. if (doUse) use(self, hit)
  190. }
  191. if (hit) hit = hit.value
  192. }
  193. return hit
  194. }
  195. function isStale(self, hit) {
  196. if (!hit || (!hit.maxAge && !self._maxAge)) return false
  197. var stale = false;
  198. var diff = Date.now() - hit.now
  199. if (hit.maxAge) {
  200. stale = diff > hit.maxAge
  201. } else {
  202. stale = self._maxAge && (diff > self._maxAge)
  203. }
  204. return stale;
  205. }
  206. function use(self, hit) {
  207. shiftLU(self, hit)
  208. hit.lu = self._mru++
  209. if (self._maxAge) hit.now = Date.now()
  210. self._lruList[hit.lu] = hit
  211. }
  212. function trim(self) {
  213. while (self._lru < self._mru && self._length > self._max)
  214. del(self, self._lruList[self._lru])
  215. }
  216. function shiftLU(self, hit) {
  217. delete self._lruList[hit.lu]
  218. while (self._lru < self._mru && !self._lruList[self._lru]) self._lru++
  219. }
  220. function del(self, hit) {
  221. if (hit) {
  222. if (self._dispose) self._dispose(hit.key, hit.value)
  223. self._length -= hit.length
  224. self._itemCount--
  225. delete self._cache[hit.key]
  226. shiftLU(self, hit)
  227. }
  228. }
  229. // classy, since V8 prefers predictable objects.
  230. function Entry(key, value, lu, length, now, maxAge) {
  231. this.key = key
  232. this.value = value
  233. this.lu = lu
  234. this.length = length
  235. this.now = now
  236. if (maxAge) this.maxAge = maxAge
  237. }