JAVA集合:LinkedHashMap

生活

按代码行数来评估软件开发的进度,就如同按重量来评估飞机建造的进度。

前言

之前学习过HashMap,它是一个无序的kv集合,底层通过数组+链表(JDK8:数组+链表+红黑树)实现,今天要学习的叫做LinkedHashMap,这是一个特殊的HashMap,他继承自HashMap,在HashMap的基础上又给每个entry增加了前驱和后继,使之成为一个有序的HashMap,而且可以通过某个参数的设定,实现LRU(最近最少使用)。

成员

LinkedHashMap特有的两个成员如下:

//头结点
    private transient Entry<K,V> header;
//访问顺序 false:按插入顺序 true :按访问顺序
    private final boolean accessOrder;

LinkdedHashMap的entry除了继承父类HashMap.Entry以外还加上了前驱和后继,借此使之有序

// These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;

构造器

  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) {
        super(m);
        accessOrder = false;
    }

    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

LinkedHashMap的构造器直接调用父类HashMap,
注意HashMap的构造器里有个钩子方法      init()
在HashMap本身是空的,但是在LinkedHashMap下
做了自己这个类特有的事情,就是实例化一个head entry

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

put 方法

put方法用到HashMap的put方法,
1.HashMap的addEntry调用到子类的addEntry

void addEntry(int hash, K key, V value, int bucketIndex) {

	//里面又调到子类的createEntry
        super.addEntry(hash, key, value, bucketIndex);

        // Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;
        //这个方法返回false,不知道干嘛的,以后遇到在看
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
    }
void createEntry(int hash, K key, V value, int bucketIndex) {
// hashmap 正常的 put
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;

        //把 自己插入到链表尾部
        e.addBefore(header);
        size++;
    }


//实现把自己插入到链表尾部,
  private void addBefore(Entry<K,V> existingEntry) {
         //  以e.addBefore(header)为例
         // e 的后驱指向 header
            after  = existingEntry;
            //前驱指向head 的前驱 ,其实就是把自己插入到head与之前驱中间
            before = existingEntry.before;
//在设置我的前驱的后驱指向我
            before.after = this;
            //我的后驱的前驱指向我
            after.before = this;
        }

2.在HashMap的put方法里又有一个钩子方法,用来给子类做自己特有的东西。

 public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //如果这个key,有 覆盖value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                //执行钩子方法
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        //如果这个key没出现过,直接插入尾部没毛病
        addEntry(hash, key, value, i);
        return null;
    }
void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            //当选择了根据访问顺序排序时
            if (lm.accessOrder) {
                lm.modCount++;
                //先把自己出链表
                remove();
                //再把自己插入到尾部
                addBefore(lm.header);
            }
        }

get方法

public V get(Object key) {
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
            //get方法同样调用这个方法,在访问过后,将entry放置尾部
        e.recordAccess(this);
        return e.value;
    }

迭代器核心方法

迭代器细节不去看了,迭代器的next方法调用到这个,可以看到他是
按照顺序去遍历的,而这个顺序就是LinkedHashMap里的entry的
before和after所决定了,可以通过指定accessOrder为true,在每次get
以及通过put覆盖key时把entry放置在最后,实现LRU
 Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (nextEntry == header)
                throw new NoSuchElementException();

            Entry<K,V> e = lastReturned = nextEntry;
            nextEntry = e.after;
            return e;
        }

猜你喜欢

转载自blog.csdn.net/qq_28605513/article/details/84965254