《Java源码解析》集合框架Map之LinkedHashMap

来源:互联网 发布:日本粉底液推荐知乎 编辑:程序博客网 时间:2024/06/05 16:58

2016-12-24 16:01
以前都没有贴出来运行环境,今天加上OS和JDK版本,因为发现不同版本实现源码有些许差别:
OS : MacOS 10.11
JDK: jdk1.7-72

LinkedHashMap

1. 还是先看看LinkedHashMap类继承结构图

LinkedHashMap类继承图

首先说说我对这个类名的理解,其实我觉得就是LinkedList+HashMap特性的集合。LinkedHashMap使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。

我们知道HashMap对于插入元素的顺序并没有维护,也就是说迭代HashMap的顺序并不是元素插入的顺序,有时候我们期待一个有序的Map集合,这时LinkedHashMap产生了。它虽然增加了时间和空间上的开销,但是通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序。

2.LinkedHashMap的基本结构

LinkedHashMap继承自HashMap,首先我们应该很明确两点:
1. LinkedHashMap可以认为是HashMap+LinkedList,即它使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。
2. LinkedHashMap的基本实现思想就是—-多态,LinkedHashMap重写了其父类HashMap的很多方法以实现双向链表的功能。可以说,理解多态,再去理解LinkedHashMap原理会事半功倍;反之也是,对于LinkedHashMap原理的学习,也可以促进和加深对于多态的理解。

3. 一些重要点在LinkedHashMap上面的答案

LinkedHashMap

关注点 结论 集合底层实现的数据结构 数组(HashMap)+双链表 集合中元素是否允许为空 Key和Value都允许为空 是否允许重复的数据 Key会被覆盖,但是Value 允许重复 是否有序 有序(插入顺序与迭代顺序一致) 是否线程安全。 非线程安全

4.重要属性

前面我们已经知道了LinkedHashMap是继承自HashMap的,所以HashMap的属性,LinkedhashMap直接继承过来了,此外还有一些LinkedHashMap为了实现双链表新加的属性:

//header指针,指向双链表的头部private transient Entry<K,V> header;//accessOrder为true: 表示按照访问的顺序来,也就是谁最先访问,就排在第一位 //accessOrder为false表示按照存放顺序来,就是你put元素的时候的顺序。private final boolean accessOrder;

此外,因为要实现双向链表的特性,所以LinkedHashMap的Entry类也必须增加属性,源码如下:

private static class Entry<K,V> extends HashMap.Entry<K,V> {    //双链表的前驱和后继    Entry<K,V> before, after;    //构造函数    Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {        super(hash, key, value, next);    }    /**     * 从双向链表中删除该结点     */    private void remove() {        before.after = after;        after.before = before;    }    /**     * Inserts this entry before the specified existing entry in the list.     */    private void addBefore(Entry<K,V> existingEntry) {        after  = existingEntry;        before = existingEntry.before;        before.after = this;        after.before = this;    }    /**     * This method is invoked by the superclass whenever the value     * of a pre-existing entry is read by Map.get or modified by Map.set.     * If the enclosing Map is access-ordered, it moves the entry     * to the end of the list; otherwise, it does nothing.     */    void recordAccess(HashMap<K,V> m) {        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;        if (lm.accessOrder) {            lm.modCount++;            remove();            addBefore(lm.header);        }    }    void recordRemoval(HashMap<K,V> m) {        remove();    }}

列一下Entry里面有的一些属性吧:
1. K key
2. V value
3. Entry next;
4. int hash
5. Entry before//前驱
6. Entry after//后继
其中前面四个是从HashMap.Entry中继承过来的;后面两个是LinkedHashMap独有的。不要搞错了next和before、After:next是用于维护HashMap指定table[i]位置上连接的Entry的顺序的,before、After是用于维护Entry插入的先后顺序的。
还是用图表示一下,列一下属性而已:
LinkedHashMap的Entry的数据结构

LinkedHashMap的构造函数

该类最后都会调用LinkedHashMap(int initialCapacity, float loadFactor) 该方法来实现构造Map。可知里面主要是调用了父类HashMap的构造函数,只是多加了一个accessOrder = false; 设置Map的迭代顺序是按照插入的顺序。

public LinkedHashMap(int initialCapacity, float loadFactor) {    super(initialCapacity, loadFactor);    accessOrder = false;}

重要的方法:

在LinkedHashMap中由于多态的特性重写了很多方法,下面主要分析以下方法:

void addEntry(int hash, K key, V value, int bucketIndex)void createEntry(int hash, K key, V value, int bucketIndex) public V get(Object key)void init()void transfer(HashMap.Entry[] newTable, boolean rehash)

init()方法

该方法被HashMap构造器调用,在hashMap中该方法是空函数体,由于在LinkedHashMap中被override重写了,所以初始化LInkedHashMap时调用的是LinkedHashMap中的init()方法。这就是典型的多态的应用。具体源码如下:

void init() {    header = new Entry<>(-1, null, null, null);    header.before = header.after = header;}

里面也就做了一件事,初始化维护LinkedHashMap有序性的双向链表,即new了一个头结点,并让header指向这个头结点,这里这个头结点的key和value都是null,其next也是null。

addEntry(int hash, K key, V value, int bucketIndex)方法

我们发现在LinkedHashMap中并没有重写put()方法,说明LinkedHashMap的put()操作调用的HashMap中的put()方法,但是我们却发现,在HashMap中的put()方法中调用的addEntry(hash, key, value, i);方法在LinkedHashMap中被重写了,我们看看重写之后的源码:

void addEntry(int hash, K key, V value, int bucketIndex) {    //先调用HashMap中的addEntry()函数    super.addEntry(hash, key, value, bucketIndex);    // Remove eldest entry if instructed    Entry<K,V> eldest = header.after;    if (removeEldestEntry(eldest)) {        removeEntryForKey(eldest.key);    }}

调用HashMap中的addEntry()函数中又在LinkedHashMap中重写了createEntry(int hash, K key, V value, int bucketIndex) ,在LinkedHashMap中如下:

void createEntry(int hash, K key, V value, int bucketIndex) {    HashMap.Entry<K,V> old = table[bucketIndex];    Entry<K,V> e = new Entry<>(hash, key, value, old);    table[bucketIndex] = e;    e.addBefore(header);    size++;}

函数中前三行和HashMap中没什么区别,区别在于多执行了一个
e.addBefore(header); 这个函数,这个函数主要在双向链表中是在header结点前面插入e结点,其实也就是在双向链表的尾部插入了新节点e。

/** * 在链表中指定结点existingEntry前面插入该实体this。 */private void addBefore(Entry<K,V> existingEntry) {    after  = existingEntry;    before = existingEntry.before;    before.after = this;    after.before = this;}

2.get(Object key)

get()方法在LinkedHashMap中被重写了。

public V get(Object key) {    //1. 根据key获取到实体    Entry<K,V> e = (Entry<K,V>)getEntry(key);    if (e == null)        return null;    e.recordAccess(this);    return e.value;}

其实该方法和hashMap中的几乎一模一样,唯一的区别在于增加了这么一句:
e.recordAccess(this);

void recordAccess(HashMap<K,V> m) {    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;    //将结点移动到双向链表的末尾    if (lm.accessOrder) {        lm.modCount++;        remove();        addBefore(lm.header);    }}

在LinkedHashMap中,由于需要有顺序需要维护,因此,当accessOrder = true 时,则需要调用recordAccess(this)方法将此节点放到双向链表的末尾。而如果accessOrder = false.则完全与HashMap类中的get方法一模一样。

小结

LinkedHashMap 和hashMap 功能基本一样,都是维护的键值对集合,连遍历 以及方法都类似,唯一的区别在于HashMap 里面的元素是根据hash值来决定存放位置的,是无序的,而LinkedHashMap 维护的是一个按顺序存放的双向链表,是有序的。

因此,记住,他们的区别在于:LinkedHashMap是“数组和双向链表”的结合体,而HashMap是“数组和单向链表”的结合体就够了。

测试

@Testpublic void testMap(){    //无序    Map<String, String> map = new HashMap<>();    map.put("1","1");    map.put("2","2");    map.put("3","3");    map.put("4","4");    map.put("5","5");    Set<Map.Entry<String,String>> set = map.entrySet();    Iterator<Map.Entry<String,String>> it = set.iterator();    while (it.hasNext()){        System.out.println(it.next());    }}@Testpublic void testMapLinked(){    //有序    Map<String, String> map = new LinkedHashMap<String,String>();    map.put("1","1");    map.put("2","2");    map.put("3","3");    map.put("4","4");    map.put("5","5");    Set<Map.Entry<String,String>> set = map.entrySet();    Iterator<Map.Entry<String,String>> it = set.iterator();    while (it.hasNext()){        System.out.println(it.next());    }}

结果:
HashMap:
HashMap

LinkedHashMap
LinkedHashMap

0 0