java 集合—— Map 实现类之 LinkedHashMap

LinkedHashMap 概述 

源码基于jdk 1.7.81

LinkedHashMap 继承自 HashMap,实现了 Map 接口,所以它具有 Map 的所有方法和特性,同时 LinkedHashMap 继承了 HashMap 所有非私有的成员变量方法。

​
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
{

    private static final long serialVersionUID = 3801124242820219131L;

    private transient Entry<K,V> header;//头结点
   
     //为true时,按照访问顺序排序,为false时,按照插入顺    序排序。
    private final boolean accessOrder; 
    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);
        }
 
}  

​

LinkedHashMap中 Entry 节点的成员变量

  • final K key

  • V value;

  • Entry<K,V> next

  • final int hash

  • Entry<K,V> before,

  • Entry<K,V> after;

前四个成员变量也就是红色的是从 HashMap 中继承来的,后面两个成员变量是 LinkedList 中独有的,前四个构成了 HashMap的结构,解决哈希冲突,后两个使得集合有序。

LinkedHashMap的构造方法

LinkedHashMap的构造方法基本上都是继承父类 HashMap的,它的参数 accessOrder 一般来说默认为 false,也就是按照插入顺序来排序。

   //继承自HashMap(int initialCapacity, float loadFactor);accessOrder 默认为false
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    //继承自 HashMap(int initialCapacity);accessOrder 默认为false
    public LinkedHashMap(int initialCapacity) {
	super(initialCapacity);
        accessOrder = false;
    }

    //继承自HashMap();accessOrder 默认为false
    public LinkedHashMap() {
	super();
        accessOrder = false;
    }

    //继承自HashMap(Map<? extends K, ? extends V> m);accessOrder 默认为false
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super(m);
        accessOrder = false;
    }

   //accessOrder 自己传入
    public LinkedHashMap(int initialCapacity,
			 float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

LinkedHashMap 的数据结构

LinkedHashMap 和 HashMap 一样,都是采用数组+链表的形式,不同的是 LinkedHashMap 在这种结构下,又使用 LinkedList 维护了它的插入先后顺序。

双向链表构成了它的插入顺序。

LinkedHashMap的基本操作

put 方法

这个put 方法是继承自 HashMap 的,当集合中已经存在key 键,则覆盖原来的value,这和 HashMap 的操作一样,但当集合中不存在key 键是,增加元素的 addEntry()方法调用的是 LinkedList 重写的方法。

public V put(K key, V value) {
    	//当 key 为 null
        if (key == null)
            return putForNullKey(value);
		//得到 hash 值
        int hash = hash(key.hashCode());
		//获得索引
        int i = indexFor(hash, table.length);
		//遍历索引位置的链表
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
			//如果链表中已经存在当前的key,覆盖即可
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
		//如果当前链表没有就增加这个键值对到集合中
        addEntry(hash, key, value, i);
        return null;
    }

 下面就是addEntry(int hash, K key, V value, int bucketIndex) 方法,其中createEntry(hash, key, value, bucketIndex);方法保证了插入顺序的先后。

/**
     * 增加一个元素到链表中
     * bucketIndex 索引值
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {
    	//把新的键值对按照尾插法插在双向链表的尾部
        createEntry(hash, key, value, bucketIndex);
		
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        } else {
        	//扩容
            if (size >= threshold)
                resize(2 * table.length);
        }
    }

 createEntry(int hash, K key, V value, int bucketIndex) 先保留了原来索引位置的节点,然后将新的节点放在索引位置,这样保证了HashMap的结构,后面调用addBefore(header)方法将新结点插入到双向链表中去,保证了LinkedList 结构。


void createEntry(int hash, K key, V value, int bucketIndex) {
		//原来索引位置存的节点
		HashMap.Entry<K,V> old = table[bucketIndex];
		//创造一个新的节点,存储待插入的键值对,
		Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
		//把当前的键值对放在索引位置。
        table[bucketIndex] = e;
		//用尾插法把新结点插在双向链表的尾部
        e.addBefore(header);
        size++;
    }

 addBefore(Entry<K,V> existingEntry)  采用尾插法,把新结点插入到了双向链表的尾部,保证了顺序。

/**
        * existingEntry 在这里指的是header
        * 这里采用的是尾插法,构成的是一个链表
        */
        private void addBefore(Entry<K,V> existingEntry) {
			//后继为header
			after  = existingEntry;
			//前驱为header 的前驱
            before = existingEntry.before;			
            before.after = this;
			after.before = this;
        }

get()方法

LinkedList 的 get() 是重写了HashMap 的 get() 方法,LinkedList 的 get() 方法里面增加了判断 accessOrder 的语句,当 accessOrder 为 true 时,就将刚刚访问的节点移动到双向链表的尾部。实现了按照访问顺序排序。

    /**
  	 * 根据 key 值来获取value
  	 */
    public V get(Object key) {
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
		//节点不存在
		if (e == null)
            return null;
		//accessOrder 的判断
        e.recordAccess(this);
        return e.value;
    }

    void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
			//accessOrder 为true时
            if (lm.accessOrder) {
                lm.modCount++;
				//将当前节点删掉
                remove();
				//把当前节点放在header 的前面,也就是双向链表的尾部。
                addBefore(lm.header);
            }
        }

remove() 方法

LinkedHashMap 的remove() 方法继承自 HashMap 的 remove() 方法。

  //删除元素
    public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }

    final Entry<K,V> removeEntryForKey(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        int i = indexFor(hash, table.length);//得到索引值
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
				//LinkedHashMap 重写了这个方法,从双向链表中删除这个节点。
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }
        return e;
    }

 e.recordRemoval(this) 方法调用了LinkedHashMap 的remove()方法,在双向链表中删除了这个节点。

示例

public class TestDemo7 {
	public static void main(String[] args) {
		LinkedHashMap<Integer,Integer> link = 
		new LinkedHashMap<Integer,Integer>(16,0.75f,true);
		link.put(1, 1);
		link.put(7, 5);
		link.put(3, 4);
		link.put(5, 7);
		link.put(2, 6);
		link.put(9, 0);
		
		System.out.println(link);
		link.get(3);
		link.get(2);
		System.out.println("访问后");
		System.out.println(link);
	}

结果

{1=1, 7=5, 3=4, 5=7, 2=6, 9=0}
访问后
{1=1, 7=5, 5=7, 9=0, 3=4, 2=6}

总结

LinkedHashMap 是 HashMap 和 LinedList 的结合,HashMap 维护了数据结构,LinedList 维护了插入顺序。

LinkedHashMap 是有序的。

LinkedHashMap 可以按照插入顺序排序,也可以按照访问顺序排序。

猜你喜欢

转载自blog.csdn.net/alyson_jm/article/details/81122426