LinkedHashMap (openjdk 1.6)源码分析

本文只是学习笔记,如有错误,欢迎指出

LinkedHashMap继承HashMap,在HashMap的基础上维护一个Entry双向链表
HashMap的分析可以看上一篇

class LinkedHashMap<K,V> extends HashMap<K,V>

header是双向链表的头节点,header的after指向链表的最后一个节点,before指向第一个节点,
所以添加节点时,是添加到header的前面

private transient Entry<K,V> header;

accessOrder可以指定访问节点的时候,是否需要将被访问的节点移动到第一个节点位置

private final boolean accessOrder;

重载HashMap的init方法,这个方法在父类的构造器里调用

void init() {
    //创建header节点
    header = new Entry<K,V>(-1, null, null, null);
    //header的before和after都指向自己
    header.before = header.after = header;
}

Entry类,双向列表的节点,继承于HashMap.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);
    }

    //删除节点方法,将改节点从双向链表中断开
    //将前一个节点的after指向该节点的after
    //将后一个节点的before指向该节点的before
    private void remove() {
        before.after = after;
        after.before = before;
    }

    //将节点添加到existingEntry节点前面
    private void addBefore(Entry<K,V> existingEntry) {
        //after指向existingEntry
        after  = existingEntry;
        //before指向existingEntry的前一个
        before = existingEntry.before;
        //existingEntry的前一个的after指向自己
        before.after = this;
        //将existingEntry的before指向自己
        after.before = this;
    }

    //当节点被访问时(例如get方法)会调用这个方法
    void recordAccess(HashMap<K,V> m) {
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
        if (lm.accessOrder) {//如果accessOrder为true
            lm.modCount++;
            remove();//将自己从双向列表中断开
            addBefore(lm.header);//将自己添加到header节点前面,也就是双向链表的第一个位置
        }
    }

    //当节点被删除时调用这个方法
    void recordRemoval(HashMap<K,V> m) {
    //将自己从链表中断开
        remove();
    }
}

当调用put方法时,会调用HashMap的put方法

//HashMap的put方法
public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    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;
        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方法,做一些LinkerHashMap自己的操作

void addEntry(int hash, K key, V value, int bucketIndex) {
    //创建一个节点
    createEntry(hash, key, value, bucketIndex);

    //获取链表最后一个节点,就就是header.after所指的那个节点
    Entry<K,V> eldest = header.after;
    //如果需要,可以删除最老的节点
    if (removeEldestEntry(eldest)) {
        removeEntryForKey(eldest.key);
    } else {
        //如果大小超过阈值则扩容
        if (size >= threshold)
            resize(2 * table.length);
    }
}

创建节点

void createEntry(int hash, K key, V value, int bucketIndex) {
    //和HashMap的创建节点一样,放到对应的table桶上
    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++;
}

判断是否需要删除最老的节点,也就是最少访问到的节点,默认是false
可以和accessOrder设置为true和重写这个方法来实现最近最少使用缓存(LRU)

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

get操作,如果有必要会移动到header的before位置(accessOrder为true)

public V get(Object key) {
    Entry<K,V> e = (Entry<K,V>)getEntry(key);
    if (e == null)
        return null;
    e.recordAccess(this);
    return e.value;
}

删除操作,使用的是HashMap的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的Entry重写了这个方法
            //被删除时,会从链表中删除
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }

    return e;
}

resize操作,使用的是HashMap的resize
resize时会调用这个方法,LinkerHashMap重写了这个方法,因为用链表访问所以节点,性能更好

void transfer(HashMap.Entry[] newTable) {
    int newCapacity = newTable.length;
    //从链表的最后一个节点开始,遍历到第一个节点
    for (Entry<K,V> e = header.after; e != header; e = e.after) {
        //找到节点所在的桶位置
        int index = indexFor(e.hash, newCapacity);
        //将当前桶的链表指向该节点的next
        e.next = newTable[index];
        //将节点放到桶上
        newTable[index] = e;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值