cache_lib 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +19 -0
- data/README.md +122 -0
- data/Rakefile +8 -0
- data/cache_lib.gemspec +25 -0
- data/lib/cache_lib/basic_cache.rb +109 -0
- data/lib/cache_lib/fifo_cache.rb +46 -0
- data/lib/cache_lib/lirs_cache.rb +214 -0
- data/lib/cache_lib/lru_cache.rb +82 -0
- data/lib/cache_lib/safe_basic_cache.rb +7 -0
- data/lib/cache_lib/safe_fifo_cache.rb +7 -0
- data/lib/cache_lib/safe_lirs_cache.rb +7 -0
- data/lib/cache_lib/safe_lru_cache.rb +7 -0
- data/lib/cache_lib/safe_sync.rb +98 -0
- data/lib/cache_lib/util_hash.rb +29 -0
- data/lib/cache_lib/version.rb +3 -0
- data/lib/cache_lib.rb +32 -0
- data/test/cache_lib/test_basic_cache.rb +131 -0
- data/test/cache_lib/test_cache_lib.rb +38 -0
- data/test/cache_lib/test_fifo_cache.rb +56 -0
- data/test/cache_lib/test_lirs_cache.rb +106 -0
- data/test/cache_lib/test_lru_cache.rb +68 -0
- data/test/cache_lib/test_safe_basic_cache.rb +10 -0
- data/test/cache_lib/test_safe_fifo_cache.rb +10 -0
- data/test/cache_lib/test_safe_lirs_cache.rb +10 -0
- data/test/cache_lib/test_safe_lru_cache.rb +10 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 49951bebe666d8d905c8cb74e5b1324f95f460da
|
4
|
+
data.tar.gz: eb58345e35655ea65121a69d22f6d936c9eae506
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 51a1d55a39dbacf762ab5b1b58275de0018fb968d7b200e7dbddbed4f159e1f3691ebd96232999a484fcffc355f9703922f3bc3f43017dd66076a0ce5c48bd28
|
7
|
+
data.tar.gz: f38f39b35db8a674c052184ba4ee32e8b7c640d06f56574ef5d528f9c4e1356ae38610949b4329d55cf48cc370b00e5007296aeab4826f3df09a1d4ba6a45607
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2015 Kaijah Hougham
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# CacheLib
|
2
|
+
#### A Ruby caching library implementing Basic, FIFO, LRU and LIRS caches.
|
3
|
+
|
4
|
+
##### CacheLib is young library and breaking api changes are still possible. Please read the release notes before upgrading.
|
5
|
+
CacheLib currently implements basic, FIFO, LRU and LIRS caches along with offering thread safe implementations of each. Originally a LIRS cache feature fork of [LruRedux](https://github.com/SamSaffron/lru_redux), CacheLib was built to provide a clean base for implementing the LIRS cache in Ruby, along with offering user friendly api.
|
6
|
+
|
7
|
+
##### Basic:
|
8
|
+
The basic cache is the simplest cache available and forms the base for the others. It does NOT have an eviction strategy and will store however much it is given.
|
9
|
+
|
10
|
+
##### FIFO:
|
11
|
+
The FIFO cache adds to the basic cache with a simple First In First Our eviction strategy to limit the cache size to a user set limit.
|
12
|
+
|
13
|
+
##### LRU:
|
14
|
+
The LRU cache further adds to the previous caches with a Least Recently Used eviction strategy. The primary difference between FIFO and LRU is that LRU will refresh currently cached items if they are requested again.
|
15
|
+
|
16
|
+
##### LIRS:
|
17
|
+
The LIRS cache implements the Low Inter-Reference Recency Set Replacement Policy developed by Song Jiang and Xiaodong Zhang. Please reference the [original paper](http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.116.2184) for more information.
|
18
|
+
|
19
|
+
##### CacheLib is Ruby >= 1.9.3 only.
|
20
|
+
If you are looking for a LRU cache that supports an earlier version or just want an alternative LRU cache, please take a look at [LruRedux](https://github.com/SamSaffron/lru_redux).
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
### Creating a cache
|
24
|
+
```ruby
|
25
|
+
require 'cache_lib'
|
26
|
+
|
27
|
+
# Basic
|
28
|
+
cache = CacheLib.create :basic
|
29
|
+
|
30
|
+
# FIFO with a limit of 100
|
31
|
+
cache = CacheLib.create :fifo, 100
|
32
|
+
|
33
|
+
# LRU with a limit of 100
|
34
|
+
cache = CacheLib.create :lru, 100
|
35
|
+
|
36
|
+
# LIRS with a cache limit of 100, made up of a S limit of 95 and a Q limit of 5.
|
37
|
+
cache = CacheLib.create :lirs, 95, 5
|
38
|
+
```
|
39
|
+
|
40
|
+
### Using the cache
|
41
|
+
```ruby
|
42
|
+
# Cache methods remain the same across all variations
|
43
|
+
cache = CacheLib.create :lru, 100
|
44
|
+
|
45
|
+
# .get is the primary method for accessing the cache. It requires a key and block that will generate the value that is stored if the key is not cached.
|
46
|
+
cache.get(:a) { 'Apple' }
|
47
|
+
# => 'Apple'
|
48
|
+
cache.get(:b) { 'Beethoven' }
|
49
|
+
# => 'Beethoven'
|
50
|
+
cache.get(:a) { 'Apricot' }
|
51
|
+
# => 'Apple'
|
52
|
+
|
53
|
+
# .store takes a key and value and implements the functionality of Hash#store. Will refresh the key in LRU and LIRS caches.
|
54
|
+
cache.store(:c, 'Chopin')
|
55
|
+
# => 'Chopin'
|
56
|
+
cache.store(:a, 'Mozart')
|
57
|
+
# => 'Mozart'
|
58
|
+
|
59
|
+
# []= is equivalent to .store.
|
60
|
+
cache[:d] = 'Denmark'
|
61
|
+
# => 'Denmark'
|
62
|
+
|
63
|
+
# .lookup takes a key and returns either the value if the key is cached or nil if it is not.
|
64
|
+
cache.lookup(:c)
|
65
|
+
# => 'Chopin'
|
66
|
+
cache.lookup(:t)
|
67
|
+
# => nil
|
68
|
+
|
69
|
+
# [] is equivalent to .lookup.
|
70
|
+
cache[:a]
|
71
|
+
# => 'Mozart'
|
72
|
+
|
73
|
+
# .fetch is similar to .lookup, but takes an optional block that will execute if the key is not cached.
|
74
|
+
cache.fetch(:c)
|
75
|
+
# => 'Chopin'
|
76
|
+
cache.fetch(:r) { 21 * 2 }
|
77
|
+
# => 42
|
78
|
+
|
79
|
+
# .evict takes a key and will remove the key from the cache and return the associated value. Returns nil is the key is not cached.
|
80
|
+
cache.evict(:d)
|
81
|
+
# => 'Denmark'
|
82
|
+
cache.evict(:r)
|
83
|
+
# => nil
|
84
|
+
|
85
|
+
# .each takes each key value pair and applies the given block to them.
|
86
|
+
cache.each { |_, value| puts value }
|
87
|
+
# => 'Chopin'
|
88
|
+
# => 'Mozart'
|
89
|
+
# => 'Beethoven'
|
90
|
+
|
91
|
+
# .key? takes a key and returns true if it is cached and false if it is not.
|
92
|
+
cache.key?(:a)
|
93
|
+
# => true
|
94
|
+
cache.key?(:g)
|
95
|
+
# => false
|
96
|
+
|
97
|
+
# .to_a returns an array of arrays of key value pairs that are cached. Basic, FIFO and LIRS are insertion ordered while LRU is recency ordered, starting with newest.
|
98
|
+
cache.to_a
|
99
|
+
# => [[:c, 'Chopin'], [:a, 'Mozart'], [:b, 'Beethoven']]
|
100
|
+
|
101
|
+
# .keys returns an array of the keys that are cached.
|
102
|
+
cache.keys
|
103
|
+
# => [:c, :a, :b]
|
104
|
+
|
105
|
+
# .values returns an array of the values that are cached.
|
106
|
+
cache.values
|
107
|
+
# => ['Chopin', 'Mozart', 'Beethoven']
|
108
|
+
|
109
|
+
# .size returns the current number of items that are cached.
|
110
|
+
cache.size
|
111
|
+
# => 3
|
112
|
+
|
113
|
+
# .clear removes all items from the cache.
|
114
|
+
cache.clear
|
115
|
+
# => nil
|
116
|
+
```
|
117
|
+
|
118
|
+
## Copyright
|
119
|
+
|
120
|
+
Copyright (c) 2015 Kaijah Hougham
|
121
|
+
|
122
|
+
See [LICENSE.txt](LICENSE.txt) for details.
|
data/Rakefile
ADDED
data/cache_lib.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path('../lib/cache_lib/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = 'cache_lib'
|
7
|
+
gem.version = CacheLib::VERSION
|
8
|
+
gem.summary = 'A Ruby Caching Library'
|
9
|
+
gem.description = 'A Ruby caching library implementing Basic, FIFO, LRU and LIRS caching strategies.'
|
10
|
+
gem.license = 'MIT'
|
11
|
+
gem.authors = 'Kaijah Hougham'
|
12
|
+
gem.email = 'github@seberius.com'
|
13
|
+
gem.homepage = 'https://github.com/seberius/cache_lib'
|
14
|
+
|
15
|
+
gem.required_ruby_version = '>= 1.9.3'
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($RS)
|
18
|
+
gem.executables = gem.files.grep(/bin/).map { |f| File.basename(f) }
|
19
|
+
gem.test_files = gem.files.grep(/test/)
|
20
|
+
gem.require_paths = ['lib']
|
21
|
+
|
22
|
+
gem.add_development_dependency 'bundler', '~> 1.7'
|
23
|
+
gem.add_development_dependency 'rake', '~> 10'
|
24
|
+
gem.add_development_dependency 'minitest', '~> 5.0'
|
25
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module CacheLib
|
2
|
+
class BasicCache
|
3
|
+
attr_reader :limit
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@limit = nil
|
7
|
+
|
8
|
+
@cache = UtilHash.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize_copy(source)
|
12
|
+
source_raw = source.raw
|
13
|
+
|
14
|
+
@limit = source_raw[:limit]
|
15
|
+
|
16
|
+
@cache = source_raw[:cache]
|
17
|
+
end
|
18
|
+
|
19
|
+
def limit=(_)
|
20
|
+
@limit = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(key)
|
24
|
+
value = hit(key)
|
25
|
+
|
26
|
+
if value
|
27
|
+
value
|
28
|
+
else
|
29
|
+
miss(key, yield)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def store(key, value)
|
34
|
+
miss(key, value)
|
35
|
+
end
|
36
|
+
|
37
|
+
def lookup(key)
|
38
|
+
@cache[key]
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch(key)
|
42
|
+
has_key = true
|
43
|
+
value = @cache.fetch(key) { has_key = false }
|
44
|
+
|
45
|
+
if has_key
|
46
|
+
value
|
47
|
+
else
|
48
|
+
yield if block_given?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def evict(key)
|
53
|
+
@cache.delete(key)
|
54
|
+
end
|
55
|
+
|
56
|
+
def clear
|
57
|
+
@cache.clear
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def each
|
62
|
+
@cache.each do |pair|
|
63
|
+
yield pair
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def key?(key)
|
68
|
+
@cache.key?(key)
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_a
|
72
|
+
@cache.to_a.reverse!
|
73
|
+
end
|
74
|
+
|
75
|
+
def keys
|
76
|
+
@cache.keys.reverse!
|
77
|
+
end
|
78
|
+
|
79
|
+
def values
|
80
|
+
@cache.values.reverse!
|
81
|
+
end
|
82
|
+
|
83
|
+
def size
|
84
|
+
@cache.size
|
85
|
+
end
|
86
|
+
|
87
|
+
def raw
|
88
|
+
{ limit: @limit,
|
89
|
+
cache: @cache.clone }
|
90
|
+
end
|
91
|
+
|
92
|
+
def inspect
|
93
|
+
"#{self.class} currently caching #{@cache.size} items."
|
94
|
+
end
|
95
|
+
|
96
|
+
alias_method :[], :lookup
|
97
|
+
alias_method :[]=, :store
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
def hit(key)
|
102
|
+
@cache[key]
|
103
|
+
end
|
104
|
+
|
105
|
+
def miss(key, value)
|
106
|
+
@cache[key] = value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module CacheLib
|
2
|
+
class FifoCache < BasicCache
|
3
|
+
def initialize(*args)
|
4
|
+
limit, _ = args
|
5
|
+
|
6
|
+
fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
|
7
|
+
limit.nil? || limit < 1
|
8
|
+
|
9
|
+
@limit = limit
|
10
|
+
|
11
|
+
@cache = UtilHash.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def limit=(args)
|
15
|
+
limit, _ = args
|
16
|
+
fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
|
17
|
+
limit.nil? || limit < 1
|
18
|
+
|
19
|
+
@limit = limit
|
20
|
+
|
21
|
+
resize
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
"#{self.class} with a limit of #{@limit} "\
|
26
|
+
"currently caching #{@cache.size} items."
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :[], :lookup
|
30
|
+
alias_method :[]=, :store
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def resize
|
35
|
+
@cache.delete(@cache.tail) while @cache.size > @limit
|
36
|
+
end
|
37
|
+
|
38
|
+
def miss(key, value)
|
39
|
+
@cache[key] = value
|
40
|
+
|
41
|
+
@cache.pop_tail if @cache.size > @limit
|
42
|
+
|
43
|
+
value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module CacheLib
|
2
|
+
class LirsCache < BasicCache
|
3
|
+
def initialize(*args)
|
4
|
+
s_limit, q_limit = args
|
5
|
+
|
6
|
+
fail ArgumentError 'S Limit must be 1 or greater.' if
|
7
|
+
s_limit.nil? || s_limit < 1
|
8
|
+
fail ArgumentError 'Q Limit must be 1 or greater.' if
|
9
|
+
q_limit.nil? || q_limit < 1
|
10
|
+
|
11
|
+
@s_limit = s_limit
|
12
|
+
@q_limit = q_limit
|
13
|
+
@limit = s_limit + q_limit
|
14
|
+
|
15
|
+
@cache = UtilHash.new
|
16
|
+
@stack = UtilHash.new
|
17
|
+
@queue = UtilHash.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize_copy(source)
|
21
|
+
source_raw = source.raw
|
22
|
+
|
23
|
+
@limit = source_raw[:limit]
|
24
|
+
@s_limit = source_raw[:s_limit]
|
25
|
+
@q_limit = source_raw[:q_limit]
|
26
|
+
|
27
|
+
@cache = source_raw[:cache]
|
28
|
+
@stack = source_raw[:stack]
|
29
|
+
@queue = source_raw[:queue]
|
30
|
+
end
|
31
|
+
|
32
|
+
def limit=(args)
|
33
|
+
s_limit, q_limit = args
|
34
|
+
|
35
|
+
fail ArgumentError 'S Limit must be 1 or greater.' if
|
36
|
+
s_limit.nil? || s_limit < 1
|
37
|
+
fail ArgumentError 'Q Limit must be 1 or greater.' if
|
38
|
+
q_limit.nil? || q_limit < 1
|
39
|
+
|
40
|
+
@s_limit = s_limit
|
41
|
+
@q_limit = q_limit
|
42
|
+
@limit = s_limit + q_limit
|
43
|
+
|
44
|
+
resize
|
45
|
+
end
|
46
|
+
|
47
|
+
def get(key)
|
48
|
+
if @cache.key?(key)
|
49
|
+
hit(key)
|
50
|
+
else
|
51
|
+
miss(key, yield)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def store(key, value)
|
56
|
+
if @cache.key?(key)
|
57
|
+
@cache[key] = value
|
58
|
+
hit(key)
|
59
|
+
else
|
60
|
+
miss(key, value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def lookup(key)
|
65
|
+
hit(key) if @cache.key?(key)
|
66
|
+
end
|
67
|
+
|
68
|
+
def fetch(key)
|
69
|
+
if @cache.key?(key)
|
70
|
+
hit(key)
|
71
|
+
else
|
72
|
+
yield if block_given?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def evict(key)
|
77
|
+
return unless @cache.key?(key)
|
78
|
+
|
79
|
+
value = @cache.delete(key)
|
80
|
+
|
81
|
+
if @queue.key?(key)
|
82
|
+
@queue.delete(key)
|
83
|
+
else
|
84
|
+
promote_hir if @queue.size > 0
|
85
|
+
|
86
|
+
trim_stack
|
87
|
+
end
|
88
|
+
|
89
|
+
value
|
90
|
+
end
|
91
|
+
|
92
|
+
def clear
|
93
|
+
@cache.clear
|
94
|
+
@stack.clear
|
95
|
+
@queue.clear
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
|
99
|
+
def raw
|
100
|
+
{ limit: @limit,
|
101
|
+
s_limit: @s_limit,
|
102
|
+
q_limit: @q_limit,
|
103
|
+
cache: @cache.clone,
|
104
|
+
stack: @stack.clone,
|
105
|
+
queue: @queue.clone }
|
106
|
+
end
|
107
|
+
|
108
|
+
def inspect
|
109
|
+
"#{self.class} with a limit of #{@limit}, "\
|
110
|
+
"s_limit of #{@s_limit} and q_limit of #{@q_limit} "\
|
111
|
+
"currently caching #{@cache.size} items."
|
112
|
+
end
|
113
|
+
|
114
|
+
alias_method :[], :lookup
|
115
|
+
alias_method :[]=, :store
|
116
|
+
|
117
|
+
protected
|
118
|
+
|
119
|
+
def trim_stack
|
120
|
+
s_tail = @stack.tail
|
121
|
+
while @queue.key?(s_tail) || !@cache.key?(s_tail)
|
122
|
+
@stack.delete(s_tail)
|
123
|
+
s_tail = @stack.tail
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def promote_hir
|
128
|
+
key = @queue.head
|
129
|
+
|
130
|
+
@stack.set_tail(key, nil) unless @stack.key?(key)
|
131
|
+
@queue.delete(key)
|
132
|
+
end
|
133
|
+
|
134
|
+
def pop_tail
|
135
|
+
key = @queue.tail
|
136
|
+
|
137
|
+
@queue.delete(key)
|
138
|
+
@cache.delete(key)
|
139
|
+
end
|
140
|
+
|
141
|
+
def pop_stack
|
142
|
+
key = @stack.tail
|
143
|
+
|
144
|
+
@cache.delete(key)
|
145
|
+
trim_stack
|
146
|
+
end
|
147
|
+
|
148
|
+
def demote_lir
|
149
|
+
key = @stack.tail
|
150
|
+
|
151
|
+
@stack.delete(key)
|
152
|
+
@queue.set_head(key, nil)
|
153
|
+
trim_stack
|
154
|
+
end
|
155
|
+
|
156
|
+
def resize
|
157
|
+
s_size = 0
|
158
|
+
|
159
|
+
@stack.each do |key, _|
|
160
|
+
s_size += 1 if @cache.key?(key) && !@queue.key?(key)
|
161
|
+
end
|
162
|
+
|
163
|
+
promote_hir while (s_size < @s_limit && @queue.size > 0) && s_size += 1
|
164
|
+
pop_tail while @queue.size > 0 && @cache.size > @limit
|
165
|
+
pop_stack while @cache.size > @limit && s_size -= 1
|
166
|
+
demote_lir while s_size > @s_limit && s_size -= 1
|
167
|
+
end
|
168
|
+
|
169
|
+
def hit(key)
|
170
|
+
value = @cache[key]
|
171
|
+
|
172
|
+
if @stack.key?(key)
|
173
|
+
if @queue.key?(key)
|
174
|
+
@stack.refresh(key)
|
175
|
+
@queue.delete(key)
|
176
|
+
|
177
|
+
demote_lir
|
178
|
+
else
|
179
|
+
s_tail_key = @stack.tail
|
180
|
+
@stack.refresh(key)
|
181
|
+
|
182
|
+
trim_stack if s_tail_key == key
|
183
|
+
end
|
184
|
+
else
|
185
|
+
@stack.set_head(key, nil)
|
186
|
+
@queue.refresh(key)
|
187
|
+
end
|
188
|
+
|
189
|
+
value
|
190
|
+
end
|
191
|
+
|
192
|
+
def miss(key, value)
|
193
|
+
if @cache.size < @s_limit
|
194
|
+
@cache[key] = value
|
195
|
+
@stack.set_head(key, nil)
|
196
|
+
else
|
197
|
+
pop_tail if @queue.size >= @q_limit
|
198
|
+
|
199
|
+
@cache[key] = value
|
200
|
+
|
201
|
+
if @stack.key?(key)
|
202
|
+
@stack.refresh(key)
|
203
|
+
|
204
|
+
demote_lir
|
205
|
+
else
|
206
|
+
@stack.set_head(key, nil)
|
207
|
+
@queue.set_head(key, nil)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
value
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module CacheLib
|
2
|
+
class LruCache < BasicCache
|
3
|
+
def initialize(*args)
|
4
|
+
limit, _ = args
|
5
|
+
|
6
|
+
fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
|
7
|
+
limit.nil? || limit < 1
|
8
|
+
|
9
|
+
@limit = limit
|
10
|
+
|
11
|
+
@cache = UtilHash.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def limit=(args)
|
15
|
+
limit, _ = args
|
16
|
+
|
17
|
+
fail ArgumentError "Cache Limit must be 1 or greater: #{limit}" if
|
18
|
+
limit.nil? || limit < 1
|
19
|
+
|
20
|
+
@limit = limit
|
21
|
+
|
22
|
+
resize
|
23
|
+
end
|
24
|
+
|
25
|
+
def get(key)
|
26
|
+
has_key = true
|
27
|
+
value = @cache.delete(key) { has_key = false }
|
28
|
+
if has_key
|
29
|
+
@cache[key] = value
|
30
|
+
else
|
31
|
+
miss(key, yield)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def store(key, value)
|
36
|
+
@cache.delete(key)
|
37
|
+
miss(key, value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def lookup(key)
|
41
|
+
has_key = true
|
42
|
+
value = @cache.delete(key) { has_key = false }
|
43
|
+
@cache[key] = value if has_key
|
44
|
+
end
|
45
|
+
|
46
|
+
def fetch(key)
|
47
|
+
has_key = true
|
48
|
+
value = @cache.delete(key) { has_key = false }
|
49
|
+
if has_key
|
50
|
+
@cache[key] = value
|
51
|
+
else
|
52
|
+
yield if block_given?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
"#{self.class} with a limit of #{@limit} "\
|
58
|
+
"currently caching #{@cache.size} items."
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :[], :lookup
|
62
|
+
alias_method :[]=, :store
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
def resize
|
67
|
+
@cache.pop_tail while @cache.size > @limit
|
68
|
+
end
|
69
|
+
|
70
|
+
def hit(key)
|
71
|
+
@cache.refresh(key)
|
72
|
+
end
|
73
|
+
|
74
|
+
def miss(key, value)
|
75
|
+
@cache[key] = value
|
76
|
+
|
77
|
+
@cache.pop_tail if @cache.size > @limit
|
78
|
+
|
79
|
+
value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|