JDK 1.4 更新内容

2018/10/17

1.4版本新增加了LinkedHashMap和LinkedHashSet两个新类,同时重构了之前Collection的一些代码,优化和封装了一下。

LinkedHashMap

有序的HashMap,继承自HashMap,使用双向链表来保存Entry链表节点。新插入节点时,如果节点重复已存在,会返回直接返回true并不会更新旧的节点???。数据结构跟HashMap一样,只是Entry节点多了一个previous指针,指向上一个节点???。重载了父类HashMap.Entry内部类。

使用链表保存每个节点插入的顺序

???跟TreeMap的性能对比和应用场景??? TreeMap插入的时候就进行排序(存储有序),所有数据存储是有序的,遍历直接输出有序元素。 LinkedHashMap插入的时候不进行排序(遍历有序),所有数据存储跟HashMap一致,遍历的时候才进行排序。

LinkedHashMap.Entry

数据结构:一个Entry[]数组,一个头节点双向链表。

重写了HashMap.Entry节点,实现成了双线链表节点;

private transient Entry header;

// HashMap.Entry 实现 是一个单向链表
static class Entry implements Map.Entry {
	final Object key;
	Object value;
	final int hash;
	Entry next;

	/**
	* Create new entry.
	*/
	Entry(int h, Object k, Object v, Entry n) { 
		value = v; 
		next = n;
		key = k;
		hash = h;
	}
}

// LinkedHashMap.Entry 实现 实现成了双向链表
private static class Entry extends HashMap.Entry {

    // 用来标识节点的顺序的,迭代器通过节点的before、after来有序查找元素
    Entry before, after;

	// LinkedHashMap.Entry的构造函数没有提供before节点引用?
	Entry(int hash, Object key, Object value, HashMap.Entry next) {
        super(hash, key, value, next); // 调用父类HashMap.Entry构造函数
    }

}

LinkedHashMap.Entry继承了HashMap.Entry,所以LinkedHashMap的Entry结构大体如下

// LinkedHashMap的Entry结构定义模型大致如下
Entry {
	final Object key,
	Object value,
	final int hash,
	Entry next,  // 以上从父类继承,Entry数组节点
	Entry before, after; // 新增链表
};

构造函数

LinkedHashMap直接调用父类的构造函数,HashMap构造函数在1.4版本中新增了一个空的init方法,该方法由子类实现。HashMap在构造函数里最终会调用init方法。

LinkedHashMap重载了HashMap的init方法,在init方法里实例化了Entry节点header。

private transient Entry header;

private final boolean accessOrder; // true-访问节点排序access-order;false-插入节点时排序insertion-order ??? 默认是false

public LinkedHashMap() {
	super(); // 调用父类的构造函数实现,初始化Entry数组
	accessOrder = false; // 基于插入排序,即按照节点put的顺序访问
}

// 该方法由父类构造函数调用
void init() {
	// 创建一个header节点
	header = new Entry(-1, null, null, null);
	header.before = header.after = header;
}

accessOrder

LinkedHashMap提供两种数据存储排序方式:插入顺序(flase 默认)和访问顺序(true)。

  1. 插入顺序,指的是,节点按照put时的顺序进行排序,遍历时根据put时的顺序输出节点内容
  2. 访问顺序,指的是,按get的顺序排序,如果节点被get方法访问,则将该节点添加到链表末尾

    访问顺序的应用场景,最终会输出一个按照从近期最少访问到最多访问次数的顺序。

put

LinkedHashMap.addEntry

JDK1.4 HashMap对put操作进行了代码重构,将新增节点操作封装到了addEntry方法,子类LinkedHashMap重写了addEntry方法,实现了自定义的新增节点操作。

  1. HashMap在JDK 1.4版本中对代码做了大部分抽取封装,方便子类重写父类的实现。
  2. HashMap的addEntry方法、HashMap.Entry的recordAccess和recordRemoval方法都提供了类似模板策略,具体实现由子类实现。

LinkedHashMap的addEntry方法流程

  1. 创建一个Entry节点
  2. 将新节点添加到链表末尾
  3. 判断是否需要扩容
// 重写了父类HashMap的addEntry方法
// HashMap.put方法最终会调用addEntry实现节点新增
void addEntry(int hash, Object key, Object value, int bucketIndex) {
	// 创建节点
	createEntry(hash, key, value, bucketIndex);

	// Remove eldest entry if instructed, else grow capacity if appropriate
	Entry eldest = header.after;
	// 这个方法有意思了,是否需要将最老的元素删除,一般是针对链表容量不够,需要自动删除最老节点策略
	if (removeEldestEntry(eldest)) {
		removeEntryForKey(eldest.key);
	} else {
		if (size >= threshold) 
			// 扩容
			resize(2 * table.length);
	}
}

// 重写了父类HashMap的createEntry方法
void createEntry(int hash, Object key, Object value, int bucketIndex) {
	Entry e = new Entry(hash, key, value, table[bucketIndex]);
	table[bucketIndex] = e;
	// addBefore方法将当前节点插入到链表末尾
	e.addBefore(header);	// 这里是重要区别
	size++;
}

// 默认不需要删除最老的节点,子类可以重写该方法,根据策略删除不需要的节点
protected boolean removeEldestEntry(Map.Entry eldest) {
	return false;
}

LinkedHashMap.Entry.addBefore

private static class Entry extends HashMap.Entry {

    // 用来标识节点的顺序的,迭代器通过节点的before、after来有序查找元素
    Entry before, after;

	Entry(int hash, Object key, Object value, HashMap.Entry next) {
        super(hash, key, value, next);
    }

	// 将当前元素before/after表示顺序的指针指向指定的元素(即Header头节点)。
	private void addBefore(Entry existingEntry) {
		after  = existingEntry;	// 当前元素的after指向header节点
		before = existingEntry.before;	// 当前元素的before指向header节点before指针指向的对象,即倒序第二个添加的元素
		before.after = this;	// 将上一个元素的after指针指向当前元素
		after.before = this;	// 将header节点的before指针指向当前元素

		// 通过这四个步骤,完成每次插入新节点能够记录下插入的顺序。
	}
}

get

LinkedHashMap重写了HashMap的get方法

  1. 通过HashMap方法获取到Entry
  2. 根据访问策略更新Entry顺序
public Object get(Object key) {
	// 调用父类的get方法,对应Entry节点
	Entry e = (Entry)getEntry(key);
	if (e == null)
		return null;
	// 根据访问策略更新节点顺序
	e.recordAccess(this);
	return e.value;
}

LinkedHashMap.Entry.recordAcess方法

如果accessOrder为true,访问有序,则将get的Entry节点移动到链表末尾

注意,这里的移动只是改变链表指针的顺序,并不会去改变Entry数组的位置。

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

remove

LinkedHashMap直接使用父类HashMap的remove方法实现节点删除操作,并重写HashMap.Entry.recordRemoval方法。节点从集合中删除时,同时从链表也删除。

rehash

// 扩容到新的Entry数组
void transfer(HashMap.Entry[] newTable) {
	int newCapacity = newTable.length;
	for (Entry e = header.after; e != header; e = e.after) {	// 跟1.2一样倒序遍历Entry数组
		int index = indexFor(e.hash, newCapacity);
		e.next = newTable[index];
		newTable[index] = e;
	}
}

迭代器

HashMap的迭代器也进行了重构。JDK1.2的迭代器为HashIterator,支持3种类型的迭代器(KYES/VALUES/ENTRIES)。JDK1.4将迭代器拆分成3个类(KeyIterator/ValueIterator/EntryIterator),不同类型的迭代器直接实例化对应对象。

LinkedHashMap重写了这三个迭代器,并自定义了自己的迭代器LinkedHashIterator。

LinkedHashIterator

LinkedHashMap重写了HashMap的迭代器实现。还是一样,看它是如何实现hasNext/next/remove三个方法的。

LinkedHashIterator.hashNext()

LinkedHashIterator.nextEntry()

LinkedHashIterator.remove()

// 还重新实现了迭代器 LinkedHashIterator
private abstract class LinkedHashIterator implements Iterator {
	Entry nextEntry    = header.after;
	Entry lastReturned = null;

	int expectedModCount = modCount;

	public boolean hasNext() {
        return nextEntry != header;
	}

	Entry nextEntry() {
	    if (modCount != expectedModCount)	// 迭代过程中不能修改集合
			throw new ConcurrentModificationException();
        if (nextEntry == header)
            throw new NoSuchElementException();

		Entry e = lastReturned = nextEntry;	// lastReturned 保存当前节点,用于remove方法删除当前几点
		nextEntry = e.after;	// 获取下一个节点
		return e;
	}

	public void remove() {
	    if (lastReturned == null)
			throw new IllegalStateException();
	    if (modCount != expectedModCount)
			throw new ConcurrentModificationException();

		LinkedHashMap.this.remove(lastReturned.key);	// 删除当前节点
		lastReturned = null;
		expectedModCount = modCount;
	}
}

LinkedHashSet

跟HashSet一样,内部是通过引用LinkedHashMap来实现的

// HashSet 新增了一个构造函数,内部直接创建LinkedHashMap来实现。
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
	map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

NIO

Buffer,Channel,Selector。

这里是一个重点

Post Directory