LinkedHashMap基于HashMap实现,继承了HashMap.,存储数据的方式和HashMap一致(将键值对映射为entry对象).
- 不同之处
LinkedHashMap还在内部维护了一个链表.
LinkedHashMap的内部数据结构类Entry继承了HashMap的Entry类,额外添加了before节点和after节点,还额外实现了remove,addBefore,recordAccess,recordRemoval方法用于构造链表
这个链表是怎么形成的?#
1.初始化链表头##
LinkedHashMap在实例化的时候会指定超类的构造器并进行实例化,超类实例化时会以多态的方式(动态绑定)来调用LinkedHashMap中重写的init方法,init方法被用来初始化链表的头结点.
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init()//HashMap中的init()方法是空的,但是子类继承该类后通过重写的方式实现init方法.HashMap类可以说一个基础实现,它有几个方法都是空的,是为子类实现做准备.
}
@Override
void init() {
header = new Entry<>(-1, null, null, null);//构造一个空的entry
header.before = header.after = header;//头节点和尾节点都指向自身.
}
2.向链表中添加数据##
- LinkedHashMap中还有一个关键的布尔类型变量accessOrder,默认为false,值为false时,向LinkedHashMap插入数据其实是调用HashMap的put方法.
- 对于put方法如果,key已经在HashMap中,那么仅仅用新的值替换旧的值并返回旧的值.
- 反之就插入一个新的键值对,即在HashMap中构造并加入一个新的entry.
- 那么插入一个entry实质上是调用addEntry方法,该方法不仅在HashMap中有实现,在LinkedHashMap中亦有实现.LinkedHashMap的实现在父类的基础上更加入了将entry插入链表的操作.
- 所以LinkedHashMap插入数据时不仅向HashMap的table数组中插入一个entry,而且将该entry放入了LinkedHashMap自身实现的链表中.
- 值为true时,会在LinkedHashMap执行put,putAll,get操作时,改变链表结构,将最近访问的entry放在链表的尾部.
//HashMap的addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
//HashMap上调用的相应的方法都会被重写
//LinkedHashMap的addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);//在HashMap的基础上更完善了自身的功能
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {//removeEldestEntry可以由我们自己来重写,下面将会实现该效果
removeEntryForKey(eldest.key);
}
//LinkedHashMap的createEntry方法
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];//对应位置的Entry被提取出来
Entry<K,V> e = new Entry<>(hash, key, value, old);//加入新的Entry的末尾
table[bucketIndex] = e;//新的Entry加入该位置
e.addBefore(header);//调用LinkedHashMap内部Entry类的方法
size++;
}
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry; //header被设为新的Entry的尾节点
before = existingEntry.before; //header的头结点被设为新的Entry的头结点,实际上header被初始化后,它的头结点和尾节点都为自身
before.after = this; //把新的Entry的节点同样地设为header的头结点和尾节点
after.before = this; //可以理解为两个类形成一个环形链.
}
//
执行一次put操作,LinkHashMap中执行步骤
每个数据的插入以此类推
前面说到accessOrder值为true时,会在LinkedHashMap执行put,putAll,get操作时,改变链表结构,将最近访问的entry放在链表的尾部.
起作用的方法是Entry内实现的recordAccess方法,在执行get,put,putAll方法都能调用到该方法
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove(); //断开与头结点和尾节点的连接
addBefore(lm.header);//将Entry加到链表的尾部
}
}
private void remove() {
before.after = after;
after.before = before;
}
用代码举例
LinkedHashMap<String, String> a = new LinkedHashMap<String, String>(16, 0.75f, true);//accessOrder为true
LinkedHashMap<String, String> b = new LinkedHashMap<String, String>(16, 0.75f, false);//accessOrder为false
//a和b都以相同顺序加入键值对,都调用get方法,然后查看通过Iterator输出的值
a.put("1", "1");
a.put("2", "2");
a.put("3", "3");
b.put("1", "1");
b.put("2", "2");
b.put("3", "3");
a.get("1");
b.get("1");
Iterator<Entry<String, String>> i = a.entrySet().iterator();
Iterator<Entry<String, String>> m = b.entrySet().iterator();
while(i.hasNext()) {
System.out.println("a输出的值:"+i.next().getValue());
}
while(m.hasNext()) {
System.out.println("b输出的值:"+m.next().getValue());
}
a输出的值:2
a输出的值:3
a输出的值:1
b输出的值:1
b输出的值:2
b输出的值:3
因为1被调用过,所以被放到了最后输出
在addEntry方法中还有一个可以让我们拓展的功能
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
//默认返回false,我们可以重写这个方法
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
LinkedHashMap<String, String> c = new LinkedHashMap<String, String>(){
private static final int MAX_ENTRIES = 2;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
};
c.put("1", "1");
c.put("2", "2");
c.put("3", "3");
Iterator<Entry<String, String>> n = c.entrySet().iterator();
while(n.hasNext()) {
System.out.println("c输出的值:"+n.next().getValue());
}
}
c输出的值:2
c输出的值:3
- c中值允许存在MAX_ENTRIES数量的键值对
- 再向其中添加会把最初添加的键值对删除.换句话说,map中只会存在最新的entry