LinkedHashMap的实现原理

观看该篇文章时,请先了解HashMap的底层原理!

LinkedHashM定义:

哈希表和链表实现的映射接口,具有可预测的迭代顺序。这个实现不同于HashMap,因为它维护一个遍历其所有条目的双向链表。这个链表定义了迭代顺序,这通常是键插入到映射中的顺序(插入顺序)。如果键是已经插入过的,则会直接返回true。

先给张大概的原理图:

LinkedHashMap每一次存值时,都会往链表的末尾添加数据。

类的继承关系

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

由上可知,LinkedHashMap直接继承了HashMap,所以拥有HashMap所有的性质。

主要成员变量:

accessOrder:这个链表HashMpa的迭代排序方法。true:使用请求顺序,false:使用插入顺序。

插入顺序:即抵用put和putAll方法时,根据插入顺序将数据存入双向链表中。

例子:

 Map<Object,String> map =  new LinkedHashMap();
        Map hashMap = new HashMap();
        map.put("1","a");
        map.put("2","b");
        map.put("3","c");
        Iterator iter1 = map.entrySet().iterator();
        while(iter1.hasNext()){
            Map.Entry entry = (Map.Entry)iter1.next();
            System.out.println("插入"+entry.getValue());
        }

输出结果:
插入a
插入b
插入c

请求顺序:调用get方法时,将被请求的数据,挪到双向链表的最前面。

例子:

Map<Object,String> map =  new LinkedHashMap(16,0.75F,true);
        map.put("1","a");
        map.put("2","b");
        map.put("3","c");
        Iterator iter1 = map.entrySet().iterator();
        while(iter1.hasNext()){
            Map.Entry entry = (Map.Entry)iter1.next();
            System.out.println("前"+entry.getValue());
        }
        map.get("1");
        Iterator iter2 = map.entrySet().iterator();
        while(iter2.hasNext()){
            Map.Entry entry = (Map.Entry)iter2.next();
            System.out.println("后"+entry.getValue());
        }

输出结果:
前a
前b
前c
后b
后c
后a

看输出结果可知,请求的数据会被重新排序到双向链表最后。

链表结点对象

同时关注一下LinkedHashMap中的链表Entry对象,该对象继承与HashMap的Entry,并加了两个独有属性before和after
    Entry
            before:存在自己插入之前插入的条目
            after:关联在自己插入之后插入的条目。
            hash:和HashMap中用法一样,存该key的hash
            key:和HashMap中用法一样,存key
            value:和HashMap中用法一样,存value。
            next:和HashMap中用法一样,存该数组结点链表的下一个条目。

构造方法

1
	public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }
	
	2
	public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }
	
	3
	public LinkedHashMap() {
        super();
        accessOrder = false;
    }
	
	4
	public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super(m);
        accessOrder = false;
    }
	
	5
	public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

看上诉构造函数可知,每个构造函数都调用了父类HashMap的构造函数。而HashMap有四个构造函数,且另外三个构造函数都会调用带有三个参数的构造方法。在该方法中,调用init()方法,在HashMap中,init()方法中没有任何实现,那么调用这个方法就是为了给子类留出一个入口,让子类通过该路口完成一些初始化工作。

创建LinkedHashMap

显而易见,LinkedHashMap重写了init()方法。

void init() {
		//双向链表的头结点,没有存值。
        header = new Entry<>(-1, null, null, null);
        header.before = header.after = header;
    }

init()方法初始化了双向链表的头结点,注意该结点永远都不会存值。

put一个key-value

LinkedHashMap并没有重写put()方法,直接使用的是HashMap中的put()方法。
HashMap的put()方法中,调用了addEntry()方法,LinkedHashMap重写了该方法。

void addEntry(int hash, K key, V value, int bucketIndex) {
			//直接调用父类即HashMap的addEntry()方法,给底层数组添加结点。
			super.addEntry(hash, key, value, bucketIndex);
			
			//removeEldestEntry(Entry<K,V> entry)该方法返回boolean值,LinkedHashMap默认返回false。我们可以重写该方法控制返回的true和false从而控制双向链表的条目的删除。
			我们可以重写该方法控制返回的true和false从而控制双向链表的条目的删除。
			Entry<K,V> eldest = header.after;
			if (removeEldestEntry(eldest)) {
			//删除指定key的值
				removeEntryForKey(eldest.key);
			}
		}

这里再说一下removeEldestEntry(Entry<K,V> entry)。
在源码的注解中,举了一个例子
private static final int MAX_ENTRIES = 100;
重写了removeEldestEntry方法,当双向链表的结点大于100时,把最老的数据先删除。
protected boolean removeEldestEntry(Map.Entry eldest) {
     return size() > MAX_ENTRIES;
}

在HashMap的addEntry()方法中,会调用createEntry()方法,LinkedHashMap也重写了这个方法

void createEntry(int hash, K key, V value, int bucketIndex) {
        /*在table(底层数组)中,添加该结点*/
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }

其他逻辑都和HashMap一样,需要注意的是e.addBefore(header);该方法就是将条目添加到双向链表里的入口。

private void addBefore(Entry<K,V> existingEntry) {
            /*将新结点关联到链表的before元素中*/
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }

LinkedHashMap的put方法就是这个流程。

get一个key-value

与put()不同,LinkedHashMap的重写了get()方法

public V get(Object key) {
        /*调用HashMap中的get()方法,查找到该结点*/
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        /*根据accessOrder,如果是true,即请求顺序,则将get的值挪到集合的最后一位,如果为false,即插入顺序,则什么都不做*/
        e.recordAccess(this);
        return e.value;
    }

再细看看recordAccess(this);方法

void recordAccess(HashMap<K,V> m) {
		LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
		if (lm.accessOrder) {
			lm.modCount++;
			remove();
			addBefore(lm.header);
		}
	}

看上诉代码可知,如果是请求顺序,则通过删除和添加将双向链表中的该结点挪到双向链表的before里。

put和get大部分逻辑都和HashMap几乎一样,只是多了维护双向链表的步骤而已。只要注意双向链表的排序顺序是请求顺序还是插入顺序!

删除remove方法

LinkedHashMap并没有看见remove(Object key)方法。

只有Entry内部类里有一个remove()方法。

private void remove() {
            before.after = after;
            after.before = before;
    }
	

但是我们平常使用的时候直接linkedHashMap.remove()就可以了,并没有linkedHashMap.Entry.remove()这么调用,所以我们调用的remove()其实是父类HashMap的,通过HashMap的remove()方法,调用了Entry的recordRemoval(HashMap<K,V> m)方法,LinkedHashMap的Entry重写了该方法。
     void recordRemoval(HashMap<K,V> m) {
        remove();
    }
    并且调用Entry中的remove()方法;
    private void remove() {
        before.after = after;
        after.before = before;
    }
    通过这个方法删除了双向链表中的值。

如果你对HashMap已经很了解了,LinkedHashMap还是很好理解的!

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值