cache_lib 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|