前面几篇从源码角度分析了HashMap put和遍历过程HashMap数据结构以及put过程,HashMap遍历原理,通过上面两篇分析HahMap遍历数据的顺序是随机的,LinkedHashMap和HashMap唯一的不同是后者遍历的是有序的。
数据结构
HashMap使用的是数组+单链表的数据结构(本质上是数组,数组的每个元素是一个单向链表),LinkedHashMap使用的是数组+双向链表数据结构。
单链表中的节点只拥有指向下个节点的指针,而双向链表中的节点同时拥有指向前一个节点和后一个节点的指针。
HashMap中定义了HashMapEntry对象来表示一个节点,该对象中有k(key),v(value),hash,next四个属性,其中next为指向下个节点的指针。
LinkedHashMap定义了LinkedHashMapEntry来表示节点,LinkedHashMapEntry继承自HashMapEntry,在它的基础上增加了before,after属性,分别指向上一个和下一个节点。
源码分析
HashMap的构造方法的最后调用了init()的空方法,LinkedHashMap中重写了init方法,这个方法中实例化了一个空的头节点。
@Override
void init() {
header = new LinkedHashMapEntry<>(-1, null, null, null);
//header的前一个节点和后一个节点都指向自己
header.before = header.after = header;
}
LinkedHashMap的put方法
LinkedHashMap和HashMap的使用方法相同。
先回顾一下HashMap的put流程:
1、先得到Key的hash值,然后根据hash值计算出该Key应该放在数组中的哪个位置。
2、根据第一步找的数组的位置,遍历该位置的链表,如果发现和当前key相同的hash值表示该key已经在链表中存在,则将旧的Value替换成新的。
3、如果当前HashMap中的size>16*0.75 则对数组进行扩容,然后拷贝旧数组数据到新的数组中(调用transfer方法)。
4、创建HashMapEntry对象插入到链表头部。(调用createEntry方法)
LinkedHashMap和HashMap put数据的第一步和第二步相同。HashMap调用transfer方法将旧表中数据拷贝到新表中,而在LinkedHashMap中重写了transfer方法。
@Override
void transfer(HashMapEntry[] newTable) {
int newCapacity = newTable.length;
//遍历旧表数据 当header.after=header时表示遍历到最后
for (LinkedHashMapEntry<K,V> e = header.after; e != header; e = e.after) {
//计算出该key在新表的索引
int index = indexFor(e.hash, newCapacity);
//将之前表中第一个节点 作为下一个节点
e.next = newTable[index];
//将节点放到数组的头位置
newTable[index] = e;
}
}
HashMap的第四步调用createEntry方法创建一个HashMapEntry并加入到链表中,LinkedLinkedHashMap中重写了createEntry方法。
void createEntry(int hash, K key, V value, int bucketIndex) {
//取出第一个节点
HashMapEntry<K,V> old = table[bucketIndex];
//创建LinkedHashMapEntry next指向 旧的头部
LinkedHashMapEntry<K,V> e = new LinkedHashMapEntry<>(hash, key, value, old);
//将创建节点 放在表的头部
table[bucketIndex] = e;
e.addBefore(header);//传入头节点
size++;
}
//找到旧节点的前一个节点作为新建的e的前一个节点
private void addBefore(LinkedHashMapEntry<K,V> existingEntry) {
//此处existingEntry=header
after = existingEntry;//after=header
//第一个节点 header.before=header 所以before=header
before = existingEntry.before;//before=header
//this为加入链表的节点
before.after = this;//header.after=e;
after.before = this;//header.before=e;
}
总结:LinkedHashMap的put过程,基本上和HashMap的put流程相同,因为LinkedHashMap使用的数据结构是双向链表,所以LinkedHashMap在数组扩容(transfer)和创建节点(createEntry)进行了特殊处理。
LinkedHashMap的遍历
HashMap遍历过程是遍历数组+单链表数据结构,找到第一个不为null的节点返回,然后根据该节点指向下个节点指针得到下一个节点信息。因为HashMap中put数据是根据Key的hash值来计算节点存放的位置的,所以HashMap遍历顺序是随机的。
LinkedHashMap遍历的方法和HashMap相同。
LinkedHashMap<String, String> hashMap = new LinkedHashMap();
Iterator<Map.Entry<String, String>> iterator1 = hashMap.entrySet().iterator();
while (iterator1.hasNext()){
iterator1.next();
}
在HashMap中在HashIterator的构造方法中找到第一个不为null的点,然后按照节点顺序依次遍历。
LinkedHashMap中重写了newEntryIterator方法实例化了一个EntryIterator继承自LinkedHashIterator,和其他地方不同LinkedHashIterator没有继承HashIterator,直接继承了Iterator。
上面分析LinkedHashMap put数据时将节点放到了header的后面,所以在遍历时只需要从header节点开始就可以按照顺序输出了。
private abstract class LinkedHashIterator<T> implements Iterator<T> {
LinkedHashMapEntry<K,V> nextEntry = header.after;//从header头部开始一次向下遍历
LinkedHashMapEntry<K,V> lastReturned = null;
/** * The modCount value that the iterator believes that the backing * List should have. If this expectation is violated, the iterator * has detected concurrent modification. */
int expectedModCount = modCount;
public boolean hasNext() {
//下一个节点 为header遍历结束
return nextEntry != header;
}
public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
LinkedHashMap.this.remove(lastReturned.key);
lastReturned = null;
expectedModCount = modCount;
}
Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == header)
throw new NoSuchElementException();
LinkedHashMapEntry<K,V> e = lastReturned = nextEntry;
nextEntry = e.after;
return e;
}
}
总结:
HashMap put数据是根据Key的hash值计算应该放到数组的第几个位置然后把数据放进去,第一个放入的数据和第二个放入的数据因为key的hash值不同,所以在数组中的位置也不同。而HashMap的遍历是找到数组中第一个不为null的节点,然后根据指向下个节点的信息可以遍历出所有节点。这样就导致HashMap的遍历顺序是随机的。
LinkedHashMap put过程和在数组中的存放的位置和HashMap相同,在LinkedHahMap中定义了header节点,放入的节点的前一个节点指向header。LinkedHashMap遍历就是根据header依次遍历,所以LinkedHashMap的遍历顺序是有序的(LinkedHasMap可以用来实现Lru算法)。