jdk源码——集合(LinkedHashMap)

     这一篇分析的是 LinkedHashMap的源码,可能有些同学会有些疑惑,HashMap和LinkedHashMap有什么区别呢?

     HashMap的集合的底层是一个散列表(数组+链表)+红黑树

    LinkedHashMap集合的底层其实也是一个散列表(数组+链表)+红黑树,只不过是链表是双向链表,HashMap的链表时单向链表结构。因为结构的差异,所以,操作会有些不同。

    我们先看一下LinkedHashMap定义便知。

 
 
/*
LinkedHashMap继承了HashMap
底层还是一个hashMap(数组+链表),只不过这里的链表是双向链表


牛逼,两个类继承,但是有不同,主要不同之处在与节点的结构不同,但是这个节点有所重中之重,就是散列表的一个结构,增删改查,就是操作的节点
将节点本身的操作提取城一个方法,如创建一个节点,设置后一个节点等方法,这样子类也同样具有这样的方法,但是因为节点结构的不同,可以自己实现
这样的方法,就是重写,利用了多态
 */
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

    LinkedHashMap的父类是HashMap类,既然是继承,LinkedHashMap已经具有了很多操作方法了。但是因为结构的不同,会稍微有些改动。

    我一直在说,底层结构的改变,那么,我们先来看一下,结构是如何改变的?

  

    /*这个类,也继承了HashMap类的内部类的Node
     * 这里的class Entry<K,V> extends HashMap.Node<K,V>,所以,class Entry<K,V>具有next字符,在HashMap中next指向的是下一个节点,
     * 但是在class Entry<K,V>中,已经有了指向前一个和后一个节点的字段,(指向的是插入时上一个或下一个节点)
       next指向的是定位后,下一个节点,同一个桶中,后一个节点
     */
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);//这是父类Node的成员变量
        }
    }

这张图就是一个散列表(数组+双向链表)


    

       ——LinkedHashMap的成员变量

    transient LinkedHashMap.Entry<K,V> head;//首
    
    transient LinkedHashMap.Entry<K,V> tail;//尾

    /*
     访问顺序(access-ordered)
     插入顺序(insertion-ordered)
     默认是插入顺序的
     * @serial
     */
    final boolean accessOrder;

     ——LinkedHashMap的构造方法(其实都是调用了父类的构造方法)

   public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;//默认是插入顺序
    }

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

    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        //这个方法稍有区别,因为HashMap的这个方法就是调用的自己的方法进行添加
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }
    //这个构造方法可以设置访问顺序
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        //这个构造方法可以指定遍历顺序
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

  

       ——LinkedHashMap的添加操作

        1.需要先创建一个节点(双向节点)

     /*
    创建一个新的节点
     */
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }

        2.维护这个节点的位置,维护的是插入顺序。

    /*
    维护刚插入节点的前后节点的关系,只是插入顺序。与在桶中的位置无关
     */
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

           3.添加操作,还是调用的是父类的添加操作,与单向链表的插入一样,因为插入顺序的维护,已经在LinkedHashMap维护了。LinkedHashMap中并没有声明一个添加方法,所以并没重写父类的添加方法,使用的依然是继承的是父类的添加方法。

           4.我们再看HashMap的源码时,会发现HashMap定义了三个方法,这个三方法都是空的,其他方法也都调用了这三个方法,当时我并不知道是什么意思,现在知道了其实就是让子类对这三个方法进行重写,说白了,就是多态。调用子类方法。

   void afterNodeRemoval(Node<K,V> e) { // unlink删除节点e
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.before = p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a == null)
            tail = b;
        else
            a.before = b;
    }

    void afterNodeInsertion(boolean evict) { // possibly remove eldest可能删除最老的节点
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

    /*
    该方法是这个类的方法,是在根据指定的key获得value时,get()方法中会调用该方法
    accessOrder为false,是默认排序(插入顺序)
    accessOrder=true时,是访问顺序,
    该方法会在指定访问顺序时,会被调用
    指定顺序了,就按该方法,最常用的将其放在链表的最后,不常用的放在链表的最前~
     */
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;//  p=e,   b<--p-->a
            p.after = null;//因为要将这个节点放在最后,所以将这个节点的after指向null

            //先判断先一个元素
            if (b == null)
                //双向链表,e是第一个节点,那么第一个要放在最后,head肯定要指向e的下一个节点
                head = a;//
            else
                //有节点,e节点不是第一个节点,将e的前一个节点的after指向e节点的后一个节点
                b.after = a;

            //在判断后一个元素
            if (a != null)
                //a!=null,e节点放在最后,a前一个元素=b
                a.before = b;
            else
                //如果a=null,b就是最后一个元素
                last = b;

            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;//
            ++modCount;
        }
    }

        ——LinkedHashMap的获取操作

     /*
    根据指定的key值获得value值
     */
    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            //证明map中肯定有key值
            afterNodeAccess(e);//
        return e.value;
    }

    /**
     * 该方法没啥用,返回指定key的value,如果没有,返回设置的值
     */
    public V getOrDefault(Object key, V defaultValue) {
       Node<K,V> e;
       if ((e = getNode(hash(key), key)) == null)
           return defaultValue;
       if (accessOrder)
           afterNodeAccess(e);
       return e.value;
   }

    ——LinkedHashMap的其他操作

    /*
     * 清除
     */
    public void clear() {
        super.clear();
        head = tail = null;
    }

    ——LinkedHashMap的映射操作

       其实都是调用的是使用了父类的成员变量keyset和values,其实还是依靠父类完成的,没啥说的。

       就这样吧,写一篇是LinkedHashSet集合,其实也没啥可以讲的,就大概了解下吧。

           



猜你喜欢

转载自blog.csdn.net/qq_36838854/article/details/80360690