1.LinkedHashMap简介:
1.HashMap元素之间是无序的,LinkedHashMap可以有序
地存储元素;
2.LinkedHashMap中的顺序分为插入顺序
和访问顺序
,可以通过accessOrder
来修改这一属性;
3.LinkedHashMap有序的特性是用双向链表来实现的;
4.LinkedHashMap不是线程安全的
2.测试:
1.HashMap无序性:
HashMap<String, Integer> map = new HashMap<>();
map.put("key1", 1);
map.put("key2", 2);
map.put("key3", 3);
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
Log.e("TTTTTag", entry.getKey() + " " + entry.getValue());
}
打印结果:
TTTTTag: key2 2
TTTTTag: key1 1
TTTTTag: key3 3
2.LinkedHashMap有序性:
LinkedHashMap<String, Integer> map = new LinkedHashMap<>(16, 0.75f, false);
map.put("key1", 1);
map.put("key2", 2);
map.put("key3", 3);
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
Log.e("TTTTTag", entry.getKey() + " " + entry.getValue());
}
打印结果:
TTTTTag: key1 1
TTTTTag: key2 2
TTTTTag: key3 3
3.get和put方法对LinkedHashMap顺序的影响:
//注意这里最后一个参数为true的意思是,按照访问顺序排序;
//如果最后一个参数为false,则get和put方法不会对链表顺序产生影响;
LinkedHashMap<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
map.put("key1", 1);
map.put("key2", 2);
map.put("key3", 3);
map.put("key4", 4);
map.put("key5", 5);
//map.put("key3", 3);
map.get("key3");
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
Log.e("TTTTTag", entry.getKey() + " " + entry.getValue());
}
打印结果:
TTTTTag: key1 1
TTTTTag: key2 2
TTTTTag: key4 4
TTTTTag: key5 5
TTTTTag: key3 3
可以看到被get
或put
访问到的元素都被移动到了链表末尾
3.LinkedHashMap源码:
LinkedHashMap源码中的指针:
next用于指向在同一个桶中hash值相同的下一个元素
before和after用于指向维护顺序所用的双向链表的上一个和下一个元素
构造方法:
public LinkedHashMap() {
// 调用HashMap的构造方法,其实就是初始化Entry[] table
super();
// 按访问顺序,默认为false,所以是基于插入排序的
accessOrder = false;
}
构造方法中调用了初始化方法:
void init() {
// 创建了一个hash=-1,key、value、next都为null的Entry
header = new Entry<>(-1, null, null, null);
// 这个Entry继承了HashMap.Entry, 它增加了两个指针,before和after;
// 让创建的Entry的before和after都指向自身;
// 其实就是创建了一个只有头部节点的双向链表
header.before = header.after = header;
}
至此,我们的到了一个空的EntryTable,和一个未放入EntryTable的head头节点;
LinkedHashMap类:
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
transient LinkedHashMapEntry<K,V> head; //最老的节点
transient LinkedHashMapEntry<K,V> tail; //最新的节点
final boolean accessOrder;
}
put方法:
1.put方法沿用了HashMap方法主体;
2.除了将元素放入hash表,还需要将这个节点加入以head为头节点的双向链表,这样就可以记录插入节点的顺序了
3.如果accessOrder为true,且要put的元素已经存在,需要将这个元素移动到链表末尾
get方法:
get方法也是沿用HashMap方法主体,如果accessOrder为true,需要将访问的元素移动到链表末尾
4.LruCacha
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
// 注意第三个参数,将accessOrder设为了true,说明LruCache记录了访问顺序
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
put方法:
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
//计算entry的大小
size += safeSizeOf(key, value);
//将已经存在的以key为键的节点置换出来
previous = map.put(key, value);
if (previous != null) {
//如果之前存在,这先减去之前那个entry所占用的内存大小
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
//如果之前存在则调用entryRemoved回调子类重写的此方法,做一些处理
entryRemoved(false, key, previous, value);
}
//根据最大的容量,计算是否需要淘汰掉最不常使用的entry
trimToSize(maxSize);
return previous;
}
淘汰最不常用的节点:
public void trimToSize(int maxSize) {
while(true) {
Object key;
Object value;
synchronized(this) {
if (this.size < 0 || this.map.isEmpty() && this.size != 0) {
throw new IllegalStateException(xxx);
}
//如果节点个数已经小于最大容量,退出循环;
if (this.size <= maxSize || this.map.isEmpty()) {
return;
}
//当超出容量的时候,循环获取链表第一个元素(最不常用节点)并删除;
Entry<K, V> toEvict = (Entry)this.map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
this.map.remove(key);
this.size -= this.safeSizeOf(key, value);
++this.evictionCount;
}
this.entryRemoved(true, key, value, (Object)null);
}
}
get方法:
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
//根据key来查询符合条件的etnry
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
//根据查到的东西新建一个节点
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
//把这个新建的节点插入LinkedHashMap,由LinkedHashMap源码可知新插入的节点位于链表尾部;
//这样链表中就可能存在两个相同的节点了
mapValue = map.put(key, createdValue);
if (mapValue != null) {
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
//为了保证链表中不出现相同的元素,从链表中删除位置靠前的那个节点
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
总结:
LinkedHashMap就是在HashMap的基础上把所有元素按顺序串成一个双向链表;
accessOrder = true时会记录访问顺序,访问包括get和put,这些操作会把访问的元素放在链表末端
accessOrder = false时只会按照元素添加顺序排序
LruCacha可以用LinkedHashMap简单实现,它只是一个记录访问顺序,且容量超限会从链表头删除元素的LinkedHashMap