Skip to content
项目
群组
代码片段
帮助
正在加载...
帮助
为 GitLab 提交贡献
登录/注册
切换导航
H
h2database
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
计划
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
Administrator
h2database
Commits
dcc2163b
提交
dcc2163b
authored
8月 01, 2012
作者:
Thomas Mueller
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
LIRS replacement algorithm
上级
a619d5c6
隐藏空白字符变更
内嵌
并排
正在显示
2 个修改的文件
包含
513 行增加
和
118 行删除
+513
-118
TestCache.java
h2/src/test/org/h2/test/store/TestCache.java
+167
-41
CacheLirs.java
h2/src/tools/org/h2/dev/store/btree/CacheLirs.java
+346
-77
没有找到文件。
h2/src/test/org/h2/test/store/TestCache.java
浏览文件 @
dcc2163b
...
@@ -6,6 +6,7 @@
...
@@ -6,6 +6,7 @@
package
org
.
h2
.
test
.
store
;
package
org
.
h2
.
test
.
store
;
import
java.util.HashMap
;
import
java.util.HashMap
;
import
java.util.Map.Entry
;
import
java.util.Random
;
import
java.util.Random
;
import
org.h2.dev.store.btree.CacheLirs
;
import
org.h2.dev.store.btree.CacheLirs
;
import
org.h2.test.TestBase
;
import
org.h2.test.TestBase
;
...
@@ -27,6 +28,7 @@ public class TestCache extends TestBase {
...
@@ -27,6 +28,7 @@ public class TestCache extends TestBase {
public
void
test
()
throws
Exception
{
public
void
test
()
throws
Exception
{
testEdgeCases
();
testEdgeCases
();
testClear
();
testGetPutPeekRemove
();
testGetPutPeekRemove
();
testLimitHot
();
testLimitHot
();
testLimitNonResident
();
testLimitNonResident
();
...
@@ -36,20 +38,44 @@ public class TestCache extends TestBase {
...
@@ -36,20 +38,44 @@ public class TestCache extends TestBase {
}
}
private
void
testEdgeCases
()
{
private
void
testEdgeCases
()
{
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
0
);
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
1
,
1
);
test
.
put
(
1
,
10
);
test
.
put
(
1
,
10
,
100
);
assertEquals
(
10
,
test
.
get
(
1
).
intValue
());
assertEquals
(
10
,
test
.
get
(
1
).
intValue
());
try
{
test
.
put
(
null
,
10
,
100
);
fail
();
}
catch
(
NullPointerException
e
)
{
// expected
}
try
{
test
.
put
(
1
,
null
,
100
);
fail
();
}
catch
(
NullPointerException
e
)
{
// expected
}
try
{
test
.
setMaxMemory
(
0
);
fail
();
}
catch
(
IllegalArgumentException
e
)
{
// expected
}
try
{
test
.
setAverageMemory
(
0
);
fail
();
}
catch
(
IllegalArgumentException
e
)
{
// expected
}
}
}
private
void
testGetPutPeekRemove
()
{
private
void
testGetPutPeekRemove
()
{
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
4
);
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
4
,
1
);
test
.
put
(
1
,
10
);
test
.
put
(
1
,
10
);
test
.
put
(
2
,
20
);
test
.
put
(
2
,
20
);
test
.
put
(
3
,
30
);
test
.
put
(
3
,
30
);
assertNull
(
test
.
peek
(
4
));
assertNull
(
test
.
peek
(
4
));
assertNull
(
test
.
get
(
4
));
assertNull
(
test
.
get
(
4
));
test
.
put
(
4
,
40
);
test
.
put
(
4
,
40
);
assertEquals
(
"mem: 4 stack: 4 3 2 1 cold: non-resident:"
,
toString
(
test
)
);
verify
(
test
,
"mem: 4 stack: 4 3 2 1 cold: non-resident:"
);
// move middle to front
// move middle to front
assertEquals
(
30
,
test
.
get
(
3
).
intValue
());
assertEquals
(
30
,
test
.
get
(
3
).
intValue
());
assertEquals
(
20
,
test
.
get
(
2
).
intValue
());
assertEquals
(
20
,
test
.
get
(
2
).
intValue
());
...
@@ -58,77 +84,167 @@ public class TestCache extends TestBase {
...
@@ -58,77 +84,167 @@ public class TestCache extends TestBase {
assertEquals
(
20
,
test
.
get
(
2
).
intValue
());
assertEquals
(
20
,
test
.
get
(
2
).
intValue
());
assertEquals
(
10
,
test
.
peek
(
1
).
intValue
());
assertEquals
(
10
,
test
.
peek
(
1
).
intValue
());
assertEquals
(
10
,
test
.
get
(
1
).
intValue
());
assertEquals
(
10
,
test
.
get
(
1
).
intValue
());
assertEquals
(
"mem: 4 stack: 1 2 3 4 cold: non-resident:"
,
toString
(
test
)
);
verify
(
test
,
"mem: 4 stack: 1 2 3 4 cold: non-resident:"
);
test
.
put
(
3
,
30
);
test
.
put
(
3
,
30
);
assertEquals
(
"mem: 4 stack: 3 1 2 4 cold: non-resident:"
,
toString
(
test
)
);
verify
(
test
,
"mem: 4 stack: 3 1 2 4 cold: non-resident:"
);
// 5 is cold; will make 4 non-resident
// 5 is cold; will make 4 non-resident
test
.
put
(
5
,
50
);
test
.
put
(
5
,
50
);
assertEquals
(
"mem: 4 stack: 5 3 1 2 cold: 5 non-resident: 4"
,
toString
(
test
)
);
verify
(
test
,
"mem: 4 stack: 5 3 1 2 cold: 5 non-resident: 4"
);
assertNull
(
test
.
peek
(
4
));
assertNull
(
test
.
peek
(
4
));
assertNull
(
test
.
get
(
4
));
assertNull
(
test
.
get
(
4
));
assertEquals
(
10
,
test
.
get
(
1
).
intValue
());
assertEquals
(
10
,
test
.
get
(
1
).
intValue
());
assertEquals
(
20
,
test
.
get
(
2
).
intValue
());
assertEquals
(
20
,
test
.
get
(
2
).
intValue
());
assertEquals
(
30
,
test
.
get
(
3
).
intValue
());
assertEquals
(
30
,
test
.
get
(
3
).
intValue
());
assertEquals
(
"mem: 4 stack: 3 2 1 cold: 5 non-resident: 4"
,
toString
(
test
)
);
verify
(
test
,
"mem: 4 stack: 3 2 1 cold: 5 non-resident: 4"
);
assertEquals
(
50
,
test
.
get
(
5
).
intValue
());
assertEquals
(
50
,
test
.
get
(
5
).
intValue
());
assertEquals
(
"mem: 4 stack: 5 3 2 1 cold: 5 non-resident: 4"
,
toString
(
test
)
);
verify
(
test
,
"mem: 4 stack: 5 3 2 1 cold: 5 non-resident: 4"
);
assertEquals
(
50
,
test
.
get
(
5
).
intValue
());
assertEquals
(
50
,
test
.
get
(
5
).
intValue
());
assertEquals
(
"mem: 4 stack: 5 3 2 cold: 1 non-resident: 4"
,
toString
(
test
)
);
verify
(
test
,
"mem: 4 stack: 5 3 2 cold: 1 non-resident: 4"
);
// remove
// remove
assertTrue
(
test
.
remove
(
5
));
assertEquals
(
50
,
test
.
remove
(
5
).
intValue
());
assertFalse
(
test
.
remove
(
5
));
assertNull
(
test
.
remove
(
5
));
assertEquals
(
"mem: 3 stack: 3 2 1 cold: non-resident: 4"
,
toString
(
test
));
verify
(
test
,
"mem: 3 stack: 3 2 1 cold: non-resident: 4"
);
assertTrue
(
test
.
remove
(
4
));
assertNull
(
test
.
remove
(
4
));
assertFalse
(
test
.
remove
(
4
));
verify
(
test
,
"mem: 3 stack: 3 2 1 cold: non-resident:"
);
assertEquals
(
"mem: 3 stack: 3 2 1 cold: non-resident:"
,
toString
(
test
));
assertNull
(
test
.
remove
(
4
));
verify
(
test
,
"mem: 3 stack: 3 2 1 cold: non-resident:"
);
test
.
put
(
4
,
40
);
test
.
put
(
4
,
40
);
test
.
put
(
5
,
50
);
test
.
put
(
5
,
50
);
assertEquals
(
"mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1"
,
toString
(
test
)
);
verify
(
test
,
"mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1"
);
test
.
get
(
5
);
test
.
get
(
5
);
test
.
get
(
2
);
test
.
get
(
2
);
test
.
get
(
3
);
test
.
get
(
3
);
test
.
get
(
4
);
test
.
get
(
4
);
assertEquals
(
"mem: 4 stack: 4 3 2 5 cold: 2 non-resident: 1"
,
toString
(
test
));
verify
(
test
,
"mem: 4 stack: 4 3 2 5 cold: 2 non-resident: 1"
);
assertTrue
(
test
.
remove
(
5
));
assertEquals
(
50
,
test
.
remove
(
5
).
intValue
());
assertEquals
(
"mem: 3 stack: 4 3 2 cold: non-resident: 1"
,
toString
(
test
));
verify
(
test
,
"mem: 3 stack: 4 3 2 cold: non-resident: 1"
);
assertTrue
(
test
.
remove
(
2
));
assertEquals
(
20
,
test
.
remove
(
2
).
intValue
());
assertTrue
(
test
.
remove
(
1
));
assertFalse
(
test
.
containsKey
(
1
));
assertEquals
(
"mem: 2 stack: 4 3 cold: non-resident:"
,
toString
(
test
));
assertNull
(
test
.
remove
(
1
));
assertFalse
(
test
.
containsKey
(
1
));
verify
(
test
,
"mem: 2 stack: 4 3 cold: non-resident:"
);
test
.
put
(
1
,
10
);
test
.
put
(
1
,
10
);
test
.
put
(
2
,
20
);
test
.
put
(
2
,
20
);
assertEquals
(
"mem: 4 stack: 2 1 4 3 cold: non-resident:"
,
toString
(
test
)
);
verify
(
test
,
"mem: 4 stack: 2 1 4 3 cold: non-resident:"
);
test
.
get
(
1
);
test
.
get
(
1
);
test
.
get
(
3
);
test
.
get
(
3
);
test
.
get
(
4
);
test
.
get
(
4
);
assertEquals
(
"mem: 4 stack: 4 3 1 2 cold: non-resident:"
,
toString
(
test
)
);
verify
(
test
,
"mem: 4 stack: 4 3 1 2 cold: non-resident:"
);
assert
True
(
test
.
remove
(
1
));
assert
Equals
(
10
,
test
.
remove
(
1
).
intValue
(
));
assertEquals
(
"mem: 3 stack: 4 3 2 cold: non-resident:"
,
toString
(
test
)
);
verify
(
test
,
"mem: 3 stack: 4 3 2 cold: non-resident:"
);
test
.
remove
(
2
);
test
.
remove
(
2
);
test
.
remove
(
3
);
test
.
remove
(
3
);
test
.
remove
(
4
);
test
.
remove
(
4
);
// test clear
// test clear
test
.
clear
();
test
.
clear
();
assertEquals
(
"mem: 0 stack: cold: non-resident:"
,
toString
(
test
));
verify
(
test
,
"mem: 0 stack: cold: non-resident:"
);
// strange situation where there is only a non-resident entry
test
.
put
(
1
,
10
);
test
.
put
(
2
,
20
);
test
.
put
(
3
,
30
);
test
.
put
(
4
,
40
);
test
.
put
(
5
,
50
);
assertTrue
(
test
.
containsValue
(
50
));
verify
(
test
,
"mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1"
);
test
.
put
(
1
,
10
);
verify
(
test
,
"mem: 4 stack: 1 5 4 3 2 cold: 1 non-resident: 5"
);
assertFalse
(
test
.
containsValue
(
50
));
test
.
remove
(
2
);
test
.
remove
(
3
);
test
.
remove
(
4
);
verify
(
test
,
"mem: 1 stack: 1 cold: non-resident: 5"
);
assertTrue
(
test
.
containsKey
(
1
));
test
.
remove
(
1
);
assertFalse
(
test
.
containsKey
(
1
));
verify
(
test
,
"mem: 0 stack: cold: non-resident: 5"
);
assertFalse
(
test
.
containsKey
(
5
));
assertTrue
(
test
.
isEmpty
());
// verify that converting a hot to cold entry will prune the stack
test
.
clear
();
test
.
put
(
1
,
10
);
test
.
put
(
2
,
20
);
test
.
put
(
3
,
30
);
test
.
put
(
4
,
40
);
test
.
put
(
5
,
50
);
test
.
get
(
4
);
test
.
get
(
3
);
verify
(
test
,
"mem: 4 stack: 3 4 5 2 cold: 5 non-resident: 1"
);
test
.
put
(
6
,
60
);
verify
(
test
,
"mem: 4 stack: 6 3 4 5 2 cold: 6 non-resident: 5 1"
);
// this will prune the stack (remove entry 5 as entry 2 becomes cold)
test
.
get
(
6
);
verify
(
test
,
"mem: 4 stack: 6 3 4 cold: 2 non-resident: 5 1"
);
}
private
void
testClear
()
{
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
40
,
10
);
for
(
int
i
=
0
;
i
<
5
;
i
++)
{
test
.
put
(
i
,
10
*
i
,
9
);
}
verify
(
test
,
"mem: 36 stack: 4 3 2 1 cold: 4 non-resident: 0"
);
for
(
Entry
<
Integer
,
Integer
>
e
:
test
.
entrySet
())
{
assertTrue
(
e
.
getKey
()
>=
1
&&
e
.
getKey
()
<=
4
);
assertTrue
(
e
.
getValue
()
>=
10
&&
e
.
getValue
()
<=
40
);
}
for
(
int
x
:
test
.
values
())
{
assertTrue
(
x
>=
10
&&
x
<=
40
);
}
for
(
int
x
:
test
.
keySet
())
{
assertTrue
(
x
>=
1
&&
x
<=
4
);
}
assertEquals
(
40
,
test
.
getMaxMemory
());
assertEquals
(
10
,
test
.
getAverageMemory
());
assertEquals
(
36
,
test
.
getUsedMemory
());
assertEquals
(
4
,
test
.
size
());
assertEquals
(
3
,
test
.
sizeHot
());
assertEquals
(
1
,
test
.
sizeNonResident
());
assertFalse
(
test
.
isEmpty
());
// changing the limit is not supposed to modify the map
test
.
setMaxMemory
(
10
);
assertEquals
(
10
,
test
.
getMaxMemory
());
test
.
setMaxMemory
(
40
);
test
.
setAverageMemory
(
1
);
assertEquals
(
1
,
test
.
getAverageMemory
());
test
.
setAverageMemory
(
10
);
verify
(
test
,
"mem: 36 stack: 4 3 2 1 cold: 4 non-resident: 0"
);
// putAll uses the average memory
test
.
putAll
(
test
);
verify
(
test
,
"mem: 40 stack: 4 3 2 1 cold: non-resident: 0"
);
test
.
clear
();
verify
(
test
,
"mem: 0 stack: cold: non-resident:"
);
assertEquals
(
40
,
test
.
getMaxMemory
());
assertEquals
(
10
,
test
.
getAverageMemory
());
assertEquals
(
0
,
test
.
getUsedMemory
());
assertEquals
(
0
,
test
.
size
());
assertEquals
(
0
,
test
.
sizeHot
());
assertEquals
(
0
,
test
.
sizeNonResident
());
assertTrue
(
test
.
isEmpty
());
}
}
private
void
testLimitHot
()
{
private
void
testLimitHot
()
{
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
100
);
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
100
,
1
);
for
(
int
i
=
0
;
i
<
300
;
i
++)
{
for
(
int
i
=
0
;
i
<
300
;
i
++)
{
test
.
put
(
i
,
10
*
i
);
test
.
put
(
i
,
10
*
i
);
}
}
assertEquals
(
1
99
,
test
.
getS
ize
());
assertEquals
(
1
00
,
test
.
s
ize
());
assertEquals
(
9
3
,
test
.
getHotSize
());
assertEquals
(
9
9
,
test
.
sizeNonResident
());
assertEquals
(
9
9
,
test
.
getNonResidentSize
());
assertEquals
(
9
3
,
test
.
sizeHot
());
}
}
private
void
testLimitNonResident
()
{
private
void
testLimitNonResident
()
{
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
4
);
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
4
,
1
);
for
(
int
i
=
0
;
i
<
20
;
i
++)
{
for
(
int
i
=
0
;
i
<
20
;
i
++)
{
test
.
put
(
i
,
10
*
i
);
test
.
put
(
i
,
10
*
i
);
}
}
assertEquals
(
"mem: 4 stack: 19 18 17 16 3 2 1 cold: 19 non-resident: 18 17 16"
,
toString
(
test
)
);
verify
(
test
,
"mem: 4 stack: 19 18 17 16 3 2 1 cold: 19 non-resident: 18 17 16"
);
}
}
private
void
testBadHashMethod
()
{
private
void
testBadHashMethod
()
{
...
@@ -159,14 +275,14 @@ public class TestCache extends TestBase {
...
@@ -159,14 +275,14 @@ public class TestCache extends TestBase {
}
}
CacheLirs
<
BadHash
,
Integer
>
test
=
CacheLirs
.
newInstance
(
size
*
2
);
CacheLirs
<
BadHash
,
Integer
>
test
=
CacheLirs
.
newInstance
(
size
*
2
,
1
);
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
test
.
put
(
new
BadHash
(
i
),
i
);
test
.
put
(
new
BadHash
(
i
),
i
);
}
}
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
if
(
i
%
3
==
0
)
{
if
(
i
%
3
==
0
)
{
assert
True
(
test
.
remove
(
new
BadHash
(
i
)
));
assert
Equals
(
i
,
test
.
remove
(
new
BadHash
(
i
)).
intValue
(
));
assert
False
(
test
.
remove
(
new
BadHash
(
i
)));
assert
Null
(
test
.
remove
(
new
BadHash
(
i
)));
}
}
}
}
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
...
@@ -181,8 +297,8 @@ public class TestCache extends TestBase {
...
@@ -181,8 +297,8 @@ public class TestCache extends TestBase {
}
}
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
if
(
i
%
3
==
0
)
{
if
(
i
%
3
==
0
)
{
assert
True
(
test
.
remove
(
new
BadHash
(
i
)
));
assert
Equals
(
i
,
test
.
remove
(
new
BadHash
(
i
)).
intValue
(
));
assert
False
(
test
.
remove
(
new
BadHash
(
i
)));
assert
Null
(
test
.
remove
(
new
BadHash
(
i
)));
}
}
}
}
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
...
@@ -198,7 +314,7 @@ public class TestCache extends TestBase {
...
@@ -198,7 +314,7 @@ public class TestCache extends TestBase {
boolean
log
=
false
;
boolean
log
=
false
;
int
size
=
20
;
int
size
=
20
;
// cache size 11 (10 hot, 1 cold)
// cache size 11 (10 hot, 1 cold)
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
size
/
2
+
1
);
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
size
/
2
+
1
,
1
);
// init the cache with some dummy entries
// init the cache with some dummy entries
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
test
.
put
(-
i
,
-
i
*
10
);
test
.
put
(-
i
,
-
i
*
10
);
...
@@ -248,7 +364,7 @@ public class TestCache extends TestBase {
...
@@ -248,7 +364,7 @@ public class TestCache extends TestBase {
int
size
=
10
;
int
size
=
10
;
Random
r
=
new
Random
(
1
);
Random
r
=
new
Random
(
1
);
for
(
int
j
=
0
;
j
<
100
;
j
++)
{
for
(
int
j
=
0
;
j
<
100
;
j
++)
{
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
size
/
2
);
CacheLirs
<
Integer
,
Integer
>
test
=
CacheLirs
.
newInstance
(
size
/
2
,
1
);
HashMap
<
Integer
,
Integer
>
good
=
New
.
hashMap
();
HashMap
<
Integer
,
Integer
>
good
=
New
.
hashMap
();
for
(
int
i
=
0
;
i
<
10000
;
i
++)
{
for
(
int
i
=
0
;
i
<
10000
;
i
++)
{
int
key
=
r
.
nextInt
(
size
);
int
key
=
r
.
nextInt
(
size
);
...
@@ -306,4 +422,14 @@ public class TestCache extends TestBase {
...
@@ -306,4 +422,14 @@ public class TestCache extends TestBase {
return
buff
.
toString
();
return
buff
.
toString
();
}
}
private
<
K
,
V
>
void
verify
(
CacheLirs
<
K
,
V
>
cache
,
String
expected
)
{
String
got
=
toString
(
cache
);
assertEquals
(
expected
,
got
);
int
mem
=
0
;
for
(
K
k
:
cache
.
keySet
())
{
mem
+=
cache
.
getMemory
(
k
);
}
assertEquals
(
mem
,
cache
.
getUsedMemory
());
}
}
}
h2/src/tools/org/h2/dev/store/btree/CacheLirs.java
浏览文件 @
dcc2163b
...
@@ -7,33 +7,82 @@
...
@@ -7,33 +7,82 @@
package
org
.
h2
.
dev
.
store
.
btree
;
package
org
.
h2
.
dev
.
store
.
btree
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.Collection
;
import
java.util.HashMap
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
/**
/**
* A cache.
* A
LIRS
cache.
* <p>
* <p>
* This implementation is not multi-threading save.
* This implementation is not multi-threading save. Null keys or null values are
*
* not allowed. There is no guard against bad hash functions, so it is important
* It is important to use a good hash function for the key (there is no guard against bad hash functions).
* to the hash function of the key is good.
* <p>
* Each each entry is assigned a distinct memory size, and the cache will try to
* use at most the specified amount of memory. The memory unit is not relevant,
* however it is suggested to use bytes as the unit.
* <p>
* <p>
* An implementation of the LIRS replacement algorithm from Xiaodong Zhang and
* An implementation of the LIRS replacement algorithm from Xiaodong Zhang and
* Song Jiang as described in
* Song Jiang as described in
* http://www.cse.ohio-state.edu/~zhang/lirs-sigmetrics-02.html with a few
* http://www.cse.ohio-state.edu/~zhang/lirs-sigmetrics-02.html with a few
* smaller changes: An additional queue for non-resident entries is used, to
* smaller changes: An additional queue for non-resident entries is used, to
* prevent unbound memory usage. The maximum size of this queue is at most the
* prevent unbound memory usage. The maximum size of this queue is at most the
* size of the rest of the stack. This implementation allows each entry to have
* size of the rest of the stack. About 5% of the mapped entries are cold.
* a distinct memory size. At most 6.25% of the mapped entries are cold.
*
* @author Thomas Mueller
*
*
* @param <K> the key type
* @param <K> the key type
* @param <V> the value type
* @param <V> the value type
*/
*/
public
class
CacheLirs
<
K
,
V
>
{
public
class
CacheLirs
<
K
,
V
>
implements
Map
<
K
,
V
>
{
/**
* The maximum memory this cache should use.
*/
private
long
maxMemory
;
private
long
maxMemory
;
private
long
currentMemory
;
/**
* The average memory used by one entry.
*/
private
int
averageMemory
;
private
int
averageMemory
;
private
int
mapSize
,
stackSize
,
queueSize
,
queue2Size
;
/**
* The currently used memory.
*/
private
long
usedMemory
;
/**
* The number of entries in the map. This includes all hot and cold entries.
*/
private
int
mapSize
;
/**
* The LIRS stack size. This includes all hot and some of the cold entries.
*/
private
int
stackSize
;
/**
* The size of the LIRS queue for resident cold entries.
*/
private
int
queueSize
;
/**
* The size of the LIRS queue for non-resident cold entries.
*/
private
int
queue2Size
;
/**
* The map entries. The size is always a power of 2.
*/
private
Entry
<
K
,
V
>[]
entries
;
private
Entry
<
K
,
V
>[]
entries
;
/**
* The bit mask that is applied to the key hash code to get the map index.
* The value is the size of the entries array minus one.
*/
private
int
mask
;
private
int
mask
;
/**
/**
...
@@ -59,43 +108,56 @@ public class CacheLirs<K, V> {
...
@@ -59,43 +108,56 @@ public class CacheLirs<K, V> {
* @param averageMemory the average memory usage of an object
* @param averageMemory the average memory usage of an object
*/
*/
private
CacheLirs
(
long
maxMemory
,
int
averageMemory
)
{
private
CacheLirs
(
long
maxMemory
,
int
averageMemory
)
{
this
.
maxMemory
=
maxMemory
;
setMaxMemory
(
maxMemory
)
;
this
.
averageMemory
=
averageMemory
;
setAverageMemory
(
averageMemory
)
;
clear
();
clear
();
}
}
/**
/**
* Create a new cache.
* Create a new cache
with the given size in number of entries
.
*
*
* @param size the maximum number of elements
* @param maxMemory the maximum memory to use (1 or larger)
* @param averageMemory the average memory (1 or larger)
* @return the cache
*/
*/
public
static
<
K
,
V
>
CacheLirs
<
K
,
V
>
newInstance
(
int
size
)
{
public
static
<
K
,
V
>
CacheLirs
<
K
,
V
>
newInstance
(
int
maxMemory
,
int
averageMemory
)
{
return
new
CacheLirs
<
K
,
V
>(
size
,
1
);
return
new
CacheLirs
<
K
,
V
>(
maxMemory
,
averageMemory
);
}
}
/**
/**
* Clear the cache.
* Clear the cache.
*/
*/
public
void
clear
()
{
public
void
clear
()
{
// calculate the size of the map array
// assume a fill factor of at most 75%
long
maxLen
=
(
long
)
(
maxMemory
/
averageMemory
/
0.75
);
long
maxLen
=
(
long
)
(
maxMemory
/
averageMemory
/
0.75
);
// the size needs to be a power of 2
long
l
=
8
;
long
l
=
8
;
while
(
l
<
maxLen
)
{
while
(
l
<
maxLen
)
{
l
+=
l
;
l
+=
l
;
}
}
// the array size is at most 2^31 elements
int
len
=
(
int
)
Math
.
min
(
1L
<<
31
,
l
);
int
len
=
(
int
)
Math
.
min
(
1L
<<
31
,
l
);
// the bit mask has all bits set
mask
=
len
-
1
;
mask
=
len
-
1
;
// initialize the stack and queue heads
stack
=
new
Entry
<
K
,
V
>();
stack
=
new
Entry
<
K
,
V
>();
stack
.
stackPrev
=
stack
.
stackNext
=
stack
;
stack
.
stackPrev
=
stack
.
stackNext
=
stack
;
queue
=
new
Entry
<
K
,
V
>();
queue
=
new
Entry
<
K
,
V
>();
queue
.
queuePrev
=
queue
.
queueNext
=
queue
;
queue
.
queuePrev
=
queue
.
queueNext
=
queue
;
queue2
=
new
Entry
<
K
,
V
>();
queue2
=
new
Entry
<
K
,
V
>();
queue2
.
queuePrev
=
queue2
.
queueNext
=
queue2
;
queue2
.
queuePrev
=
queue2
.
queueNext
=
queue2
;
// first set to null - avoiding out of memory
// first set to null - avoiding out of memory
entries
=
null
;
entries
=
null
;
@SuppressWarnings
(
"unchecked"
)
@SuppressWarnings
(
"unchecked"
)
Entry
<
K
,
V
>[]
e
=
new
Entry
[
len
];
Entry
<
K
,
V
>[]
e
=
new
Entry
[
len
];
entries
=
e
;
entries
=
e
;
currentMemory
=
0
;
mapSize
=
0
;
usedMemory
=
0
;
stackSize
=
queueSize
=
queue2Size
=
0
;
stackSize
=
queueSize
=
queue2Size
=
0
;
}
}
...
@@ -110,23 +172,39 @@ public class CacheLirs<K, V> {
...
@@ -110,23 +172,39 @@ public class CacheLirs<K, V> {
Entry
<
K
,
V
>
e
=
find
(
key
);
Entry
<
K
,
V
>
e
=
find
(
key
);
return
e
==
null
?
null
:
e
.
value
;
return
e
==
null
?
null
:
e
.
value
;
}
}
/**
* Get the memory used for the given key.
*
* @param key the key
* @return the memory, or 0 if there is no resident entry
*/
public
int
getMemory
(
K
key
)
{
Entry
<
K
,
V
>
e
=
find
(
key
);
return
e
==
null
?
null
:
e
.
memory
;
}
/**
/**
* Get an entry if the entry is cached. This method adjusts the internal
* Get an entry if the entry is cached. This method adjusts the internal
* state of the cache, to ensure commonly used entries stay in the cache.
* state of the cache, to ensure commonly used entries stay in the cache.
*
*
* @param key the key
* @param key the key
(may not be null)
* @return the value, or null if not found
* @return the value, or null if not found
*/
*/
public
V
get
(
K
key
)
{
public
V
get
(
Object
key
)
{
Entry
<
K
,
V
>
e
=
find
(
key
);
Entry
<
K
,
V
>
e
=
find
(
key
);
if
(
e
==
null
||
e
.
value
==
null
)
{
if
(
e
==
null
||
e
.
value
==
null
)
{
// either the entry was not found, or it was a non-resident entry
return
null
;
return
null
;
}
else
if
(
e
.
isHot
())
{
}
else
if
(
e
.
isHot
())
{
if
(
e
!=
stack
.
stackNext
)
{
if
(
e
!=
stack
.
stackNext
)
{
// move a hot entries to the top of the stack
// unless it is already there
boolean
wasEnd
=
e
==
stack
.
stackPrev
;
boolean
wasEnd
=
e
==
stack
.
stackPrev
;
removeFromStack
(
e
);
removeFromStack
(
e
);
if
(
wasEnd
)
{
if
(
wasEnd
)
{
// if moving the last entry, the last entry
// could not be cold, which is not allowed
pruneStack
();
pruneStack
();
}
}
addToStack
(
e
);
addToStack
(
e
);
...
@@ -134,11 +212,17 @@ public class CacheLirs<K, V> {
...
@@ -134,11 +212,17 @@ public class CacheLirs<K, V> {
}
else
{
}
else
{
removeFromQueue
(
e
);
removeFromQueue
(
e
);
if
(
e
.
stackNext
!=
null
)
{
if
(
e
.
stackNext
!=
null
)
{
// resident cold entries become hot
// if they are on the stack
removeFromStack
(
e
);
removeFromStack
(
e
);
// which means a hot entry needs to become cold
convertOldestHotToCold
();
convertOldestHotToCold
();
}
else
{
}
else
{
// cold entries that are not on the stack
// move to the front of the queue
addToQueue
(
queue
,
e
);
addToQueue
(
queue
,
e
);
}
}
// in any case, the cold entry is moved to the top of the stack
addToStack
(
e
);
addToStack
(
e
);
}
}
return
e
.
value
;
return
e
.
value
;
...
@@ -148,11 +232,11 @@ public class CacheLirs<K, V> {
...
@@ -148,11 +232,11 @@ public class CacheLirs<K, V> {
* Add an entry to the cache. This method is the same as adding an entry
* Add an entry to the cache. This method is the same as adding an entry
* with the average memory size.
* with the average memory size.
*
*
* @param key the key
* @param key the key
(may not be null)
* @param value the value
* @param value the value
(may not be null)
*/
*/
public
void
put
(
K
key
,
V
value
)
{
public
V
put
(
K
key
,
V
value
)
{
put
(
key
,
value
,
averageMemory
);
return
put
(
key
,
value
,
averageMemory
);
}
}
/**
/**
...
@@ -160,63 +244,77 @@ public class CacheLirs<K, V> {
...
@@ -160,63 +244,77 @@ public class CacheLirs<K, V> {
* yet. This method will usually mark unknown entries as cold and known
* yet. This method will usually mark unknown entries as cold and known
* entries as hot.
* entries as hot.
*
*
* @param key the key
* @param key the key
(may not be null)
* @param value the value
* @param value the value
(may not be null)
* @param memory the memory used for the given entry
* @param memory the memory used for the given entry
*/
*/
public
void
put
(
K
key
,
V
value
,
int
memory
)
{
public
V
put
(
K
key
,
V
value
,
int
memory
)
{
if
(
find
(
key
)
!=
null
)
{
if
(
value
==
null
)
{
throw
new
NullPointerException
();
}
V
old
;
Entry
<
K
,
V
>
e
=
find
(
key
);
if
(
e
==
null
)
{
old
=
null
;
}
else
{
old
=
e
.
value
;
remove
(
key
);
remove
(
key
);
}
}
Entry
<
K
,
V
>
e
=
new
Entry
<
K
,
V
>();
e
=
new
Entry
<
K
,
V
>();
e
.
key
=
key
;
e
.
key
=
key
;
e
.
value
=
value
;
e
.
value
=
value
;
e
.
memory
=
memory
;
e
.
memory
=
memory
;
int
index
=
key
.
hashCode
()
&
mask
;
int
index
=
key
.
hashCode
()
&
mask
;
e
.
chained
=
entries
[
index
];
e
.
mapNext
=
entries
[
index
];
entries
[
index
]
=
e
;
entries
[
index
]
=
e
;
currentMemory
+=
memory
;
usedMemory
+=
memory
;
if
(
currentMemory
>
maxMemory
&&
mapSize
>
0
)
{
if
(
usedMemory
>
maxMemory
&&
mapSize
>
0
)
{
// an old entry needs to be removed
evict
(
e
);
evict
(
e
);
}
}
mapSize
++;
mapSize
++;
// added entries are always added to the stack
addToStack
(
e
);
addToStack
(
e
);
return
old
;
}
}
/**
/**
* Remove an entry.
* Remove an entry.
*
*
* @param key the key
* @param key the key
(may not be null)
* @return true if the entry was found (resident or non-resident)
* @return true if the entry was found (resident or non-resident)
*/
*/
public
boolean
remove
(
K
key
)
{
public
V
remove
(
Object
key
)
{
int
hash
=
key
.
hashCode
();
int
hash
=
key
.
hashCode
();
int
index
=
hash
&
mask
;
int
index
=
hash
&
mask
;
Entry
<
K
,
V
>
e
=
entries
[
index
];
Entry
<
K
,
V
>
e
=
entries
[
index
];
if
(
e
==
null
)
{
if
(
e
==
null
)
{
return
false
;
return
null
;
}
}
V
old
;
if
(
e
.
key
.
equals
(
key
))
{
if
(
e
.
key
.
equals
(
key
))
{
entries
[
index
]
=
e
.
chained
;
old
=
e
.
value
;
entries
[
index
]
=
e
.
mapNext
;
}
else
{
}
else
{
Entry
<
K
,
V
>
last
;
Entry
<
K
,
V
>
last
;
do
{
do
{
last
=
e
;
last
=
e
;
e
=
e
.
chained
;
e
=
e
.
mapNext
;
if
(
e
==
null
)
{
if
(
e
==
null
)
{
return
false
;
return
null
;
}
}
}
while
(!
e
.
key
.
equals
(
key
));
}
while
(!
e
.
key
.
equals
(
key
));
last
.
chained
=
e
.
chained
;
old
=
e
.
value
;
last
.
mapNext
=
e
.
mapNext
;
}
}
mapSize
--;
mapSize
--;
current
Memory
-=
e
.
memory
;
used
Memory
-=
e
.
memory
;
if
(
e
.
stackNext
!=
null
)
{
if
(
e
.
stackNext
!=
null
)
{
removeFromStack
(
e
);
removeFromStack
(
e
);
}
}
if
(
e
.
isHot
())
{
if
(
e
.
isHot
())
{
// when removing a hot entry,
convert the newest cold entry to
hot,
// when removing a hot entry,
the newest cold entry gets
hot,
// so th
at we keep the number of hot entries
// so th
e number of hot entries does not change
e
=
queue
.
queueNext
;
e
=
queue
.
queueNext
;
if
(
e
!=
queue
)
{
if
(
e
!=
queue
)
{
removeFromQueue
(
e
);
removeFromQueue
(
e
);
...
@@ -228,21 +326,33 @@ public class CacheLirs<K, V> {
...
@@ -228,21 +326,33 @@ public class CacheLirs<K, V> {
removeFromQueue
(
e
);
removeFromQueue
(
e
);
}
}
pruneStack
();
pruneStack
();
return
true
;
return
old
;
}
}
/**
* Evict cold entries (resident and non-resident) until the memory limit is
* reached.
*
* @param newCold a new cold entry
*/
private
void
evict
(
Entry
<
K
,
V
>
newCold
)
{
private
void
evict
(
Entry
<
K
,
V
>
newCold
)
{
// ensure there are not too many hot entries:
// left shift of 5 is multiplication by 32, that means if there are less
// than 1/32 (3.125%) cold entries, a new hot entry needs to become cold
while
((
queueSize
<<
5
)
<
mapSize
)
{
while
((
queueSize
<<
5
)
<
mapSize
)
{
convertOldestHotToCold
();
convertOldestHotToCold
();
}
}
// the new cold entry is at the top of the queue
addToQueue
(
queue
,
newCold
);
addToQueue
(
queue
,
newCold
);
while
(
currentMemory
>
maxMemory
)
{
// the oldest resident cold entries become non-resident
while
(
usedMemory
>
maxMemory
)
{
Entry
<
K
,
V
>
e
=
queue
.
queuePrev
;
Entry
<
K
,
V
>
e
=
queue
.
queuePrev
;
current
Memory
-=
e
.
memory
;
used
Memory
-=
e
.
memory
;
removeFromQueue
(
e
);
removeFromQueue
(
e
);
e
.
value
=
null
;
e
.
value
=
null
;
e
.
memory
=
0
;
e
.
memory
=
0
;
addToQueue
(
queue2
,
e
);
addToQueue
(
queue2
,
e
);
// the size of the non-resident-cold entries needs to be limited
while
(
queue2Size
+
queue2Size
>
stackSize
)
{
while
(
queue2Size
+
queue2Size
>
stackSize
)
{
e
=
queue2
.
queuePrev
;
e
=
queue2
.
queuePrev
;
remove
(
e
.
key
);
remove
(
e
.
key
);
...
@@ -251,27 +361,41 @@ public class CacheLirs<K, V> {
...
@@ -251,27 +361,41 @@ public class CacheLirs<K, V> {
}
}
private
void
convertOldestHotToCold
()
{
private
void
convertOldestHotToCold
()
{
// the last entry of the stack is known to be hot
Entry
<
K
,
V
>
last
=
stack
.
stackPrev
;
Entry
<
K
,
V
>
last
=
stack
.
stackPrev
;
// remove from stack - which is done anyway in the stack pruning, but we
// can do it here as well
removeFromStack
(
last
);
removeFromStack
(
last
);
// adding an entry to the queue will make it cold
addToQueue
(
queue
,
last
);
addToQueue
(
queue
,
last
);
pruneStack
();
pruneStack
();
}
}
/**
* Ensure the last entry of the stack is cold.
*/
private
void
pruneStack
()
{
private
void
pruneStack
()
{
while
(
true
)
{
while
(
true
)
{
Entry
<
K
,
V
>
last
=
stack
.
stackPrev
;
Entry
<
K
,
V
>
last
=
stack
.
stackPrev
;
if
(
last
==
stack
||
last
.
isHot
())
{
if
(
last
==
stack
||
last
.
isHot
())
{
break
;
break
;
}
}
// the cold entry is still in the queue
removeFromStack
(
last
);
removeFromStack
(
last
);
}
}
}
}
private
Entry
<
K
,
V
>
find
(
K
key
)
{
/**
* Try to find an entry in the map.
*
* @param key the key
* @return the entry (might be a non-resident)
*/
private
Entry
<
K
,
V
>
find
(
Object
key
)
{
int
hash
=
key
.
hashCode
();
int
hash
=
key
.
hashCode
();
Entry
<
K
,
V
>
e
=
entries
[
hash
&
mask
];
Entry
<
K
,
V
>
e
=
entries
[
hash
&
mask
];
while
(
e
!=
null
&&
!
e
.
key
.
equals
(
key
))
{
while
(
e
!=
null
&&
!
e
.
key
.
equals
(
key
))
{
e
=
e
.
chained
;
e
=
e
.
mapNext
;
}
}
return
e
;
return
e
;
}
}
...
@@ -323,67 +447,172 @@ public class CacheLirs<K, V> {
...
@@ -323,67 +447,172 @@ public class CacheLirs<K, V> {
}
}
/**
/**
* Get the number of mapped entries (resident and non-resident).
* Get the list of keys. This method allows to view the internal state of
* the cache.
*
* @param cold if true, the keys for the cold entries are returned
* @param nonResident true for non-resident entries
* @return the key list
*/
public
List
<
K
>
keys
(
boolean
cold
,
boolean
nonResident
)
{
ArrayList
<
K
>
s
=
new
ArrayList
<
K
>();
if
(
cold
)
{
Entry
<
K
,
V
>
start
=
nonResident
?
queue2
:
queue
;
for
(
Entry
<
K
,
V
>
e
=
start
.
queueNext
;
e
!=
start
;
e
=
e
.
queueNext
)
{
s
.
add
(
e
.
key
);
}
}
else
{
for
(
Entry
<
K
,
V
>
e
=
stack
.
stackNext
;
e
!=
stack
;
e
=
e
.
stackNext
)
{
s
.
add
(
e
.
key
);
}
}
return
s
;
}
/**
* Get the number of resident entries.
*
*
* @return the number of entries
* @return the number of entries
*/
*/
public
int
getSize
()
{
public
int
size
()
{
return
mapSize
;
return
mapSize
-
queue2Size
;
}
/**
* Check whether there are any resident entries in the map.
*
* @return true if there are no keys
*/
public
boolean
isEmpty
()
{
return
size
()
==
0
;
}
/**
* Check whether there is a resident entry for the given key.
*
* @return true if the key is in the map
*/
public
boolean
containsKey
(
Object
key
)
{
Entry
<
K
,
V
>
e
=
find
(
key
);
return
e
!=
null
&&
e
.
value
!=
null
;
}
/**
* Check whether there are any keys for the given value.
*
* @return true if there is a key for this value
*/
public
boolean
containsValue
(
Object
value
)
{
return
values
().
contains
(
value
);
}
public
void
putAll
(
Map
<?
extends
K
,
?
extends
V
>
m
)
{
for
(
Map
.
Entry
<?
extends
K
,
?
extends
V
>
e
:
m
.
entrySet
())
{
put
(
e
.
getKey
(),
e
.
getValue
());
}
}
public
Set
<
K
>
keySet
()
{
HashSet
<
K
>
set
=
new
HashSet
<
K
>();
for
(
Entry
<
K
,
V
>
e
=
stack
.
stackNext
;
e
!=
stack
;
e
=
e
.
stackNext
)
{
set
.
add
(
e
.
key
);
}
for
(
Entry
<
K
,
V
>
e
=
queue
.
queueNext
;
e
!=
queue
;
e
=
e
.
queueNext
)
{
set
.
add
(
e
.
key
);
}
return
set
;
}
public
Collection
<
V
>
values
()
{
ArrayList
<
V
>
list
=
new
ArrayList
<
V
>();
for
(
K
k
:
keySet
())
{
list
.
add
(
get
(
k
));
}
return
list
;
}
public
Set
<
Map
.
Entry
<
K
,
V
>>
entrySet
()
{
HashMap
<
K
,
V
>
map
=
new
HashMap
<
K
,
V
>();
for
(
K
k
:
keySet
())
{
map
.
put
(
k
,
find
(
k
).
value
);
}
return
map
.
entrySet
();
}
}
/**
/**
* Get the number of hot entries.
* Get the number of hot entries
in the cache
.
*
*
* @return the number of entries
* @return the number of entries
*/
*/
public
int
getHotSize
()
{
public
int
sizeHot
()
{
return
mapSize
-
queueSize
-
queue2Size
;
return
mapSize
-
queueSize
-
queue2Size
;
}
}
/**
/**
* Get the number of non-resident entries.
* Get the number of non-resident entries
in the cache
.
*
*
* @return the number of entries
* @return the number of entries
*/
*/
public
int
getNonResidentSize
()
{
public
int
sizeNonResident
()
{
return
queue2Size
;
return
queue2Size
;
}
}
/**
/**
* Get the list of keys for this map. This method allows to view the internal
* Get the currently used memory.
* state of the cache.
*
*
* @param cold if true only the keys for the cold entries are returned
* @return the used memory
* @param nonResident true for non-resident entries
* @return the key set
*/
*/
public
List
<
K
>
keys
(
boolean
cold
,
boolean
nonResident
)
{
public
long
getUsedMemory
()
{
ArrayList
<
K
>
s
=
new
ArrayList
<
K
>();
return
usedMemory
;
if
(
cold
)
{
}
Entry
<
K
,
V
>
start
=
nonResident
?
queue2
:
queue
;
for
(
Entry
<
K
,
V
>
e
=
start
.
queueNext
;
e
!=
start
;
e
=
e
.
queueNext
)
{
/**
s
.
add
(
e
.
key
);
* Set the maximum memory this cache should use. This will not immediately
}
* cause entries to get removed however; it will only change the limit.
}
else
{
*
for
(
Entry
<
K
,
V
>
e
=
stack
.
stackNext
;
e
!=
stack
;
e
=
e
.
stackNext
)
{
* @param maxMemory the maximum size (1 or larger)
s
.
add
(
e
.
key
);
*/
}
public
void
setMaxMemory
(
long
maxMemory
)
{
if
(
maxMemory
<=
0
)
{
throw
new
IllegalArgumentException
(
"Max memory must be larger than 0"
);
}
}
return
s
;
this
.
maxMemory
=
maxMemory
;
}
}
/**
/**
* Get the
currently used memory
.
* Get the
maximum memory to use
.
*
*
* @return the
used
memory
* @return the
maximum
memory
*/
*/
public
long
getUsedMemory
()
{
public
long
getMaxMemory
()
{
return
currentMemory
;
return
maxMemory
;
}
/**
* Set the average memory used per entry. It is used to calculate the size
* of the map.
*
* @param averageMemory the average memory used (1 or larger)
*/
public
void
setAverageMemory
(
int
averageMemory
)
{
if
(
averageMemory
<=
0
)
{
throw
new
IllegalArgumentException
(
"Average memory must be larger than 0"
);
}
this
.
averageMemory
=
averageMemory
;
}
/**
* Get the average memory used per entry.
*
* @return the average memory
*/
public
int
getAverageMemory
()
{
return
averageMemory
;
}
}
/**
/**
* A cache entry. Each entry is either hot (low inter-reference recency;
* A cache entry. Each entry is either hot (low inter-reference recency;
*
lir), cold (high inter-reference recency; hir
), or non-resident-cold. Hot
*
LIR), cold (high inter-reference recency; HIR
), or non-resident-cold. Hot
* entries are in the stack only. Cold entries are in the queue, and may be
* entries are in the stack only. Cold entries are in the queue, and may be
* in the stack. Non-resident-cold entries have their value set to null and
* in the stack. Non-resident-cold entries have their value set to null and
* are in the stack and in the non-resident queue.
* are in the stack and in the non-resident queue.
...
@@ -392,13 +621,53 @@ public class CacheLirs<K, V> {
...
@@ -392,13 +621,53 @@ public class CacheLirs<K, V> {
* @param <V> the value type
* @param <V> the value type
*/
*/
static
class
Entry
<
K
,
V
>
{
static
class
Entry
<
K
,
V
>
{
/**
* The key.
*/
K
key
;
K
key
;
/**
* The value. Set to null for non-resident-cold entries.
*/
V
value
;
V
value
;
/**
* The estimated memory used.
*/
int
memory
;
int
memory
;
Entry
<
K
,
V
>
stackPrev
,
stackNext
;
Entry
<
K
,
V
>
queuePrev
,
queueNext
;
Entry
<
K
,
V
>
chained
;
/**
* The next entry in the stack.
*/
Entry
<
K
,
V
>
stackNext
;
/**
* The previous entry in the stack.
*/
Entry
<
K
,
V
>
stackPrev
;
/**
* The next entry in the queue (either the resident queue or the
* non-resident queue).
*/
Entry
<
K
,
V
>
queueNext
;
/**
* The previous entry in the queue.
*/
Entry
<
K
,
V
>
queuePrev
;
/**
* The next entry in the map
*/
Entry
<
K
,
V
>
mapNext
;
/**
* Whether this entry is hot. Cold entries are in one of the two queues.
*
* @return whether the entry is hot
*/
boolean
isHot
()
{
boolean
isHot
()
{
return
queueNext
==
null
;
return
queueNext
==
null
;
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论