“LinkedHashMap”的版本间的差异
Jihongchang(讨论 | 贡献) |
Jihongchang(讨论 | 贡献) |
||
(未显示同一用户的7个中间版本) | |||
第1行: | 第1行: | ||
− | <syntaxhighlight lang="java"> | + | https://www.bilibili.com/video/BV1JP411379g<syntaxhighlight lang="java"> |
package java.util; | package java.util; | ||
…… | …… | ||
第11行: | 第11行: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | [[文件:LinkedHashMap添加和取出顺序.png|无|缩略图|替代=|1373x1373像素]]结点左边的“b”是 before 的意思,是指向前驱的引用类型变量; | ||
+ | |||
+ | 结点右边的“a”是 after 的意思,是指向后继的引用类型变量; | ||
+ | |||
+ | next 则是按照 hashcode 取模之后存储元素的hash槽的位置; | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | === 测试元素添加和取出的顺序 === | ||
+ | <syntaxhighlight lang="java"> | ||
+ | package io.github.jihch; | ||
+ | |||
+ | import java.util.LinkedHashMap; | ||
+ | |||
+ | public class LinkedHashMapTest { | ||
+ | |||
+ | public static void main(String[] args) { | ||
+ | //创建水果集合 | ||
+ | LinkedHashMap<String, String> fruits = new LinkedHashMap<>(); | ||
+ | //添加水果 | ||
+ | fruits.put("apple", "苹果"); | ||
+ | fruits.put("banana", "香蕉"); | ||
+ | fruits.put("pear", "梨子"); | ||
+ | fruits.put("grape", "葡萄"); | ||
+ | fruits.put("lemon", "柠檬"); | ||
+ | fruits.forEach((k, v) -> { | ||
+ | System.out.printf("key:%s,value:%s\n", k, v); | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </syntaxhighlight><syntaxhighlight lang="console"> | ||
+ | key:apple,value:苹果 | ||
+ | key:banana,value:香蕉 | ||
+ | key:pear,value:梨子 | ||
+ | key:grape,value:葡萄 | ||
+ | key:lemon,value:柠檬 | ||
+ | </syntaxhighlight>可以发现,元素添加的顺序和取出的顺序一致 | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | === 测试元素访问的顺序 === | ||
+ | 被访问过的元素排在最后,这里的“被访问过”指的是通过<syntaxhighlight lang="java"> | ||
+ | public V get(Object key) { | ||
+ | Node<K,V> e; | ||
+ | if ((e = getNode(hash(key), key)) == null) | ||
+ | return null; | ||
+ | if (accessOrder) | ||
+ | afterNodeAccess(e); | ||
+ | return e.value; | ||
+ | } | ||
+ | </syntaxhighlight>方法获取过; | ||
+ | |||
+ | 举个例子, | ||
+ | |||
+ | 添加顺序:<"apple","苹果">,<"banana","香蕉">,<"pear","梨子">,<"grape","葡萄">,<"lemon","柠檬"> | ||
+ | |||
+ | 如果此时访问:<"apple","苹果"> | ||
+ | |||
+ | 则取出时顺序:<"banana","香蕉">,<"pear","梨子">,<"grape","葡萄">,<"lemon","柠檬">,<"apple","苹果"><syntaxhighlight lang="java"> | ||
+ | import java.util.LinkedHashMap; | ||
+ | |||
+ | public class LinkedHashMapTest { | ||
+ | |||
+ | public static void main(String[] args) { | ||
+ | //创建水果集合 | ||
+ | LinkedHashMap<String, String> fruits = new LinkedHashMap<>(16, 0.75f, true); | ||
+ | //添加水果 | ||
+ | fruits.put("apple", "苹果"); | ||
+ | fruits.put("banana", "香蕉"); | ||
+ | fruits.put("pear", "梨子"); | ||
+ | fruits.put("grape", "葡萄"); | ||
+ | fruits.put("lemon", "柠檬"); | ||
+ | |||
+ | System.out.println("访问前:"); | ||
+ | fruits.forEach((k, v) -> { | ||
+ | System.out.printf("key:%s,value:%s\n", k, v); | ||
+ | }); | ||
+ | |||
+ | fruits.get("apple"); | ||
+ | |||
+ | System.out.println("访问后:"); | ||
+ | fruits.forEach((k, v) -> { | ||
+ | System.out.printf("key:%s,value:%s\n", k, v); | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </syntaxhighlight><syntaxhighlight lang="console"> | ||
+ | 访问前: | ||
+ | key:apple,value:苹果 | ||
+ | key:banana,value:香蕉 | ||
+ | key:pear,value:梨子 | ||
+ | key:grape,value:葡萄 | ||
+ | key:lemon,value:柠檬 | ||
+ | 访问后: | ||
+ | key:banana,value:香蕉 | ||
+ | key:pear,value:梨子 | ||
+ | key:grape,value:葡萄 | ||
+ | key:lemon,value:柠檬 | ||
+ | key:apple,value:苹果 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | === LinkedHashMap 实现 LRU === | ||
+ | |||
+ | ==== LRU.java ==== | ||
+ | <syntaxhighlight lang="java"> | ||
+ | import java.util.LinkedHashMap; | ||
+ | import java.util.Map; | ||
+ | |||
+ | public class LRU<K, V> { | ||
+ | |||
+ | private static final float loadFactor = 0.75f; | ||
+ | private LinkedHashMap<K, V> map; | ||
+ | private int cacheSize; | ||
+ | |||
+ | public LRU(int cacheSize) { | ||
+ | this.cacheSize = cacheSize; | ||
+ | int capacity = (int)Math.ceil(cacheSize/loadFactor)+1; | ||
+ | map = new LinkedHashMap<K, V> (capacity, loadFactor, true) | ||
+ | { | ||
+ | private static final long serialVersionUID = 1L; | ||
+ | |||
+ | @Override | ||
+ | protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { | ||
+ | return size() > LRU.this.cacheSize; | ||
+ | } | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | public synchronized V get(K key) { | ||
+ | return map.get(key); | ||
+ | } | ||
+ | |||
+ | public synchronized void put(K key, V value) { | ||
+ | map.put(key, value); | ||
+ | } | ||
+ | |||
+ | public synchronized void clear() { | ||
+ | map.clear(); | ||
+ | } | ||
+ | |||
+ | public String toString() { | ||
+ | StringBuffer sb = new StringBuffer(""); | ||
+ | for (Map.Entry<K, V> entry:map.entrySet()) | ||
+ | { | ||
+ | sb.append(entry.getValue()+"->"); | ||
+ | } | ||
+ | return sb.toString(); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ==== LRUTest.java ==== | ||
+ | <syntaxhighlight lang="java"> | ||
+ | public class LRUTest { | ||
+ | |||
+ | public static void main(String[] args) { | ||
+ | LRU<Integer, Integer> lru = new LRU<>(5); | ||
+ | for(int i = 0; i <5; i++) { | ||
+ | lru.put(i, i); | ||
+ | } | ||
+ | System.out.println(lru); | ||
+ | lru.get(3); | ||
+ | System.out.println(lru); | ||
+ | lru.put(5, 5); | ||
+ | System.out.println(lru); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </syntaxhighlight>关键点有两处: | ||
+ | |||
+ | 一个是调用 LinkedHashMap 构造传参时给 accessOrder 传值为 true;LinkedHashMap 的有4个构造方法,其中3个构造方法都指定了 accessOrder 的值为 false,表示遍历元素的顺序会和 put 元素的顺序一致;还有一个构造方法接收 accessOrder 传参,accessOrder 值为 true 时,每一次通过 get 方法访问过的元素会放到在遍历元素顺序的末尾; | ||
+ | |||
+ | 利用 LinkedHashMap 实现 LRU 的核心是重写 removeEldestEntry 方法,当容量到达上限时,LinkedHashMap 开始按照遍历顺序移除元素,在 accessOrder 为 true 时,队列前面的元素都是相对最近没有 get 过的,就被移除了,而队尾的则都是通过 get 方法访问过的 |
2023年5月6日 (六) 12:07的最新版本
https://www.bilibili.com/video/BV1JP411379g
package java.util;
……
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
……
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
}
结点左边的“b”是 before 的意思,是指向前驱的引用类型变量;
结点右边的“a”是 after 的意思,是指向后继的引用类型变量;
next 则是按照 hashcode 取模之后存储元素的hash槽的位置;
测试元素添加和取出的顺序
package io.github.jihch;
import java.util.LinkedHashMap;
public class LinkedHashMapTest {
public static void main(String[] args) {
//创建水果集合
LinkedHashMap<String, String> fruits = new LinkedHashMap<>();
//添加水果
fruits.put("apple", "苹果");
fruits.put("banana", "香蕉");
fruits.put("pear", "梨子");
fruits.put("grape", "葡萄");
fruits.put("lemon", "柠檬");
fruits.forEach((k, v) -> {
System.out.printf("key:%s,value:%s\n", k, v);
});
}
}
key:apple,value:苹果
key:banana,value:香蕉
key:pear,value:梨子
key:grape,value:葡萄
key:lemon,value:柠檬
可以发现,元素添加的顺序和取出的顺序一致
测试元素访问的顺序
被访问过的元素排在最后,这里的“被访问过”指的是通过
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
方法获取过;
举个例子,
添加顺序:<"apple","苹果">,<"banana","香蕉">,<"pear","梨子">,<"grape","葡萄">,<"lemon","柠檬">
如果此时访问:<"apple","苹果">
则取出时顺序:<"banana","香蕉">,<"pear","梨子">,<"grape","葡萄">,<"lemon","柠檬">,<"apple","苹果">
import java.util.LinkedHashMap;
public class LinkedHashMapTest {
public static void main(String[] args) {
//创建水果集合
LinkedHashMap<String, String> fruits = new LinkedHashMap<>(16, 0.75f, true);
//添加水果
fruits.put("apple", "苹果");
fruits.put("banana", "香蕉");
fruits.put("pear", "梨子");
fruits.put("grape", "葡萄");
fruits.put("lemon", "柠檬");
System.out.println("访问前:");
fruits.forEach((k, v) -> {
System.out.printf("key:%s,value:%s\n", k, v);
});
fruits.get("apple");
System.out.println("访问后:");
fruits.forEach((k, v) -> {
System.out.printf("key:%s,value:%s\n", k, v);
});
}
}
访问前:
key:apple,value:苹果
key:banana,value:香蕉
key:pear,value:梨子
key:grape,value:葡萄
key:lemon,value:柠檬
访问后:
key:banana,value:香蕉
key:pear,value:梨子
key:grape,value:葡萄
key:lemon,value:柠檬
key:apple,value:苹果
LinkedHashMap 实现 LRU
LRU.java
import java.util.LinkedHashMap;
import java.util.Map;
public class LRU<K, V> {
private static final float loadFactor = 0.75f;
private LinkedHashMap<K, V> map;
private int cacheSize;
public LRU(int cacheSize) {
this.cacheSize = cacheSize;
int capacity = (int)Math.ceil(cacheSize/loadFactor)+1;
map = new LinkedHashMap<K, V> (capacity, loadFactor, true)
{
private static final long serialVersionUID = 1L;
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > LRU.this.cacheSize;
}
};
}
public synchronized V get(K key) {
return map.get(key);
}
public synchronized void put(K key, V value) {
map.put(key, value);
}
public synchronized void clear() {
map.clear();
}
public String toString() {
StringBuffer sb = new StringBuffer("");
for (Map.Entry<K, V> entry:map.entrySet())
{
sb.append(entry.getValue()+"->");
}
return sb.toString();
}
}
LRUTest.java
public class LRUTest {
public static void main(String[] args) {
LRU<Integer, Integer> lru = new LRU<>(5);
for(int i = 0; i <5; i++) {
lru.put(i, i);
}
System.out.println(lru);
lru.get(3);
System.out.println(lru);
lru.put(5, 5);
System.out.println(lru);
}
}
关键点有两处:
一个是调用 LinkedHashMap 构造传参时给 accessOrder 传值为 true;LinkedHashMap 的有4个构造方法,其中3个构造方法都指定了 accessOrder 的值为 false,表示遍历元素的顺序会和 put 元素的顺序一致;还有一个构造方法接收 accessOrder 传参,accessOrder 值为 true 时,每一次通过 get 方法访问过的元素会放到在遍历元素顺序的末尾;
利用 LinkedHashMap 实现 LRU 的核心是重写 removeEldestEntry 方法,当容量到达上限时,LinkedHashMap 开始按照遍历顺序移除元素,在 accessOrder 为 true 时,队列前面的元素都是相对最近没有 get 过的,就被移除了,而队尾的则都是通过 get 方法访问过的