Java源码集合类LinkedHashMap学习1

本文详细解析了LinkedHashMap类的实现原理,介绍了它是如何在HashMap的基础上扩展双向链表来保持键值对的插入顺序,并通过源码分析展示了其内部运作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LinkedHashMap类简介(jdk 1.7.0_67)

LinkedHashMap类继承了HashMap类,也就是LinkedHashMap类的功能几乎和HashMap一样。而LinkedHashMap类就是扩展了一个双向链表,使得可以按照“键-值”对插入的顺序遍历,这个是在HashMap类中遍历是没有顺序的。LinkedHashMap类可以插入null的key值和value值,以及这个类也是线程不安全的。重点是要了解它是如何实现按“键-值”对插入的顺序遍历的。

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

LinkedHashMap类中的Entry<K,V>类继承自HashMap.Entry<K,V>类源码如下:

//这里就只是多了两个属性
private static class Entry<K,V> extends HashMap.Entry<K,V> {
	// These fields comprise the doubly linked list used for iteration.
	Entry<K,V> before, after;//这两个属性用于构成双链表遍历

	Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
		super(hash, key, value, next);
	}
	//后面源代码省略
}
先看如下一段代码:

public static void main(String[] args){
	Map<String, String> linkedMap = new LinkedHashMap<String, String>();
	linkedMap.put("k1", "v1");
	linkedMap.put("k2", "v2");
	linkedMap.put("k3", "v3");
}
执行第2行代码的时候,先调用父类HashMap类的HashMap()无参构造函数初始化加载因子和阀值,源码代码如下:
//调用无参构造函数
public HashMap() {
	this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

public HashMap(int initialCapacity, float loadFactor) {
	if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal initial capacity: " +
										   initialCapacity);
	if (initialCapacity > MAXIMUM_CAPACITY)
		initialCapacity = MAXIMUM_CAPACITY;
	if (loadFactor <= 0 || Float.isNaN(loadFactor))
		throw new IllegalArgumentException("Illegal load factor: " +
										   loadFactor);

	this.loadFactor = loadFactor;
	threshold = initialCapacity;
	init();//LinkedHashMap类中重写了这个方法,所以调用的是LinkedHashMap类中的init()方法
}

//LinkedHashMap类中的init()方法
@Override
void init() {
	header = new Entry<>(-1, null, null, null);
	header.before = header.after = header;
}
执行方法init()的时候生成了一个Entry对象,就是链表的头部,为了方便理解这里假设header变量指向的引用地址是:0X000,那么执行完这个方法就生成了一个结构类似如下图的对象:


接着执行代码:linkedMap.put("k1", "v1"),LinkedHashMap类中并没有重写这个方法,它调用的是父类HashMap类中的put(K k, V v)方法,源码如下:

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;
		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);//此处调用的是LinkedHashMap类中的该方法,因为重写了该方法(体现了多态性)
	return null;
}
put方法中要做的事就是初始化table数组的容量,计算哈希值,根据哈希值算出在table数组中的索引,然后在是保存“键-值”对。关键是看看LinkedHashMap类中方法addEntry()的源码:
void addEntry(int hash, K key, V value, int bucketIndex) {
	super.addEntry(hash, key, value, bucketIndex);

	// Remove eldest entry if instructed//下面的代码是为了实现LRU算法的缓存用的,这里不用去关心
	Entry<K,V> eldest = header.after;
	if (removeEldestEntry(eldest)) {
		removeEntryForKey(eldest.key);
	}
}

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
	return false;
}
这行代码:super.addEntry(hash, key, value, bucketIndex)它是调用了父类HashMap中的方法,源代码如下:
void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);//此处调用的是LinkedHashMap类中的该方法,因为重写了该方法(体现了多态性)
}
//LinkedHashMap中的方法
void createEntry(int hash, K key, V value, int bucketIndex) {
	HashMap.Entry
  
  
   
    old = table[bucketIndex];
	Entry
   
   
    
     e = new Entry<>(hash, key, value, old);
	table[bucketIndex] = e;
	e.addBefore(header);
	size++;
}

//LinkedHashMap中的方法
private void addBefore(Entry
    
    
     
      existingEntry) {
	after  = existingEntry;
	before = existingEntry.before;
	before.after = this;
	after.before = this;
}

    
    
   
   
  
  

从上面的源码可以看出,父类中HashMap维护了“键-值”对的基本存储结构,也就是说”键—值“对存储在HashMap类中的结构一样。而重点我们就来分析LinkedHashMap类中它是如何建立双向链表的。通过上面main方法中的put代码和方法addBefore(Entry existingEntry)方法来分析。

代码:linkedMap.put("k1", "v1");假设生成Entry<>对象的地址是:0X001,根据key="k1"计算出的hash值是3214,next为null。分析忽略了具体在table数组中的位置,只分析如何建立双向链表的过程,当执行到e.addBefore(header),它的内存指向图如下所示:

1.after  = existingEntry,existingEntry它在这里始终是链表的头header,after变量指向的地址是header的地址0X000;

2.before = existingEntry.before,before变量指向的地址是:0X000;

3.before.after = this,before指向的是0X000也就是指向header对象的地址,那么before.after也就是等价于header.after指向地址:0X001;

4.依据上面3可得 after.before指向的地址是:0X001,最后的结果成了一个双向的循环链表。

代码linkedMap.put("k2", "v2")(假设生成的Entry<>对象为e2,内存地址为0X002,hash值为1243,next=null)执行的结果,组成的双向链表如下图:


每次put进来一个新的”键—值“对就生成一个Entry<>对象插入到链表header的前面组成一个前后双向的链表,这个就是LinkedHashMap类的关键点,这样我们就可以按照插入的顺序遍历”键—值“对元素了。这里用到了数据结构链表的知识,当我们对未知感到恐惧,是因为我们无知。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值