LinkedHashMap源码解析

LinkedHashMap继承了HashaMap,它拥有HashMap的所有特性。LinkedHashMap是LinkedList与HashMap的结合体。通过双向链表来保持迭代顺序,正是因为这个特性,LinkedHashMap提供了空的一个方法,重写此方法可以很好的实现Lru算法。


LinkedHashMap

LinkedHashMap默认是按照插入顺序进行排序的,它的本质还是HashMap和双向链表的结合体,它是一个将所有节点链入双向链表的HashMap。所有的put进来的节点在插入到哈希表中后,由于又定义了一个双向链表,其节点还会插入到双向链表的尾部。LinkedHashMap的特性和HashMap相同,比如最多允许一个键为空,多个value为空。它也是线程不安全的。


LinkedHashMap属性

LinkedHashMap多了三个属性,分别是双向链表的头节点和尾节点,标志符accessOrder

     // 双向链表的头节点
    transient LinkedHashMap.Entry<K,V> head;
    //  双向链表的尾节点
    transient LinkedHashMap.Entry<K,V> tail;
    //  默认是false,代表按照插入顺序迭代,如果为true,代表按照访问顺序迭代
    final boolean accessOrder;

LinkedHashMap重新定义了Entry,增加了befaore,after,用于节点插入的先后顺序,next是用来保存HashMap桶中节点的后续节点

    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);
        }
    }


LinkedHashMap构造方法

LinkedHashMap一共有5个构造方法,是在HashMap的基础上实现的。

    // 构造指定容量大小和负载因子的LinkedHashMap
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    // 构造指定初始容量大小和默认负载因子的LinkedHashMap
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    // 默认无参的构造函数 
    public LinkedHashMap() {
        super();   // 调用父类构造函数
        accessOrder = false;  // 默认为false,表示按插入顺序迭代
    }

    // 初始化负载因子,把m中的元素都添加到HashMap中
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    } 
    // 构造一个指定容量大小和负载因子,指定迭代顺序的LinkedHashMap
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }


put方法

LinkedHashMap的put继承了HashMap的put方法,只不过重写了里面的afterNodeAccess和afterNodeInsertion方法,在HasnMap中已经分析了put方法,这里直接看一下LinkedHashMap自己重写的方法。

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =     // LinkedHashMap是调用自己的Entry类
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {  // 把新添加的节点追加到双向链表的链尾
        LinkedHashMap.Entry<K,V> last = tail;  // 将链表节点赋给last
        tail = p;   // 把当前节点作为链尾节点
        if (last == null) // 如果链尾为空,代表链表为空,这是第一个添加的节点,则直接把当前节点=head = last
            head = p;
        else {  // 链表不为空,直接把当前作为链尾节点
            p.before = last;  // 把last作为当前节点的前节点
            last.after = p;   //  当前节点作为last的后节点,这样通过改变节点的引用就把当前节点作为链尾
        }
    }
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) { // 如果是TreeNode节点也是相同的操作
        TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
        linkNodeLast(p);
        return p;    
    }
void afterNodeInsertion(boolean evict) { // 插入节点后把最老的节点删除,但是removeEldestEntry方法总是返回false,
        LinkedHashMap.Entry<K,V> first;  // 并不会删除节点,如果想实现,让子类去重写该方法,给定一个条件来控制删除最老的节点
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

接着看一下afterNodeAccess方法,分析一下源码

void afterNodeAccess(Node<K,V> e) {  // 把最近访问的节点放到链尾
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {  // accessOrder如果为true,并且当前的节点不是尾节点
            LinkedHashMap.Entry<K,V> p =    //  找到当前节点的前节点和后节点
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;   // 把当前节点的后节点置为null,因为当前节点要置为尾节点,所以当前节点的后节点必定为空
            if (b == null)  // 如果当前节点的前节点为空,则把当前节点的后节点设为头节点
                head = a;
            else            // 如果当前节点前节点不为空,把当前节点的后节点作为当前节点的前节点的后节点
                b.after = a;
            if (a != null)   // 如果当前节点的后节点不为空,把当前节点的前节点作为当前节点后节点的前节点
                a.before = b;
            else           // 如果当前节点的后节点为空,那就把当前节点的前节点作为尾节点
                last = b;
            if (last == null)   // 如果尾节点为空,把当前节点作为头节点
                head = p;
            else {          // 如果尾节点不为空,那就把尾节点作为当前节点的前节点,当前节点作为尾节点的后节点
                p.before = last; 
                last.after = p;
            } 
            tail = p;     //  把当前节点作为尾节点
            ++modCount;
        }
    }
当accessOrder为true的时候,会调用这个方法,把当前节点放到链尾。


get方法

get方法调用了HashMap的getNode方法,如果没有找到该节点,直接返回null,否则,对accessOrder进行判断,如果accessOrder为true,表示按照访问顺序排序,调用afterNodeAccess方法,把当前节点放到链尾。

public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }


remove方法

remove方法在HashMap中已经删除了节点,接下来LinkedHashMap重写afteNodeRemoval方法来移除双向链表中的节点
void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMap.Entry<K,V> p =   // p为要删除的节点
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.before = p.after = null;  // 把p的前后节点都置为空,偏于GC
        if (b == null)     //如果p的前节点为空,p的后节点直接作为头节点
            head = a;
        else             // 如果p前节点不为空,p的后节点变为p的前节点的后节点
            b.after = a;
        if (a == null)   // 如果p的后节点为空,p的前节点作为尾节点
            tail = b;
        else            // 如果p的后节点不为空,p的前节点变为p的后节点的前节点
            a.before = b;
    }


Lru算法

当使用LinkedHashMap实现LRU算法时,需要调用其构造方法把accessOrder设为true,这样就开启了访问顺序排序的模式。put方法或是get方法,put方法是把key相同的节点放到链尾,get方法需要把accessOrder设为true,才能把刚找到的节点放到链尾,多次put或get后,这样头节点就是最长没被访问的节点,当节点个数满了时候,直接删除最少使用的节点即头节点即可。下面是一个实现LRU的demo

public static void main(String[] args) {
		LRU<String, Integer> lru = new LRU<>(16, 0.75f, true);
		lru.put("java", 1);
		lru.put("python", 2);
		lru.put("golang", 3);
		lru.put("c++", 4);
		lru.put("node.js", 5);
		System.out.println("LRU的大小:"+lru.size());
		System.out.println("LRU:"+lru);

	}
	public static class LRU<K, V> extends LinkedHashMap<K, V> implements Map<K, V>{
		
		public LRU(int cap,float loadFactor,boolean accessOrder) {
			super(cap, loadFactor, accessOrder);
		}
		@Override
		protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
			if (size() > 3) {
				return true;
			}
			return false;
		}
	}

运行结果为



LinkedHashMap虽然不如HashMap使用频率高,但也是一个重要的实现,我们还是应该对这种实现思想好好学习的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值