LinkedHashMap
在HashMap基础上了增加了如下内容:
每个Entry添加了before和after引用,有个链表的head 和tail ,插入时使用head before after tail 将结点Entry串联起来。Head指向插入顺序第一元素的引用,tail指向按插入顺序最后一个元素的引用。对每一个元素(Entry/Node)增加了俩个属性,before指向按插入顺序前一个元素,after指向插入顺序后一个元素的引用,next指向的是桶里面的链表的下一个
WeakHash Map
通常情况下Map里还有元素在被使用时,都不会被回收,
而WeakHash Map当里面的某个元素不再被使用时,可以被垃圾回收器回收
而
EnumMap
key值必须是枚举类型
IdentityHashMap
通常情况下,哈希值计算方式是key值对象.hasCode()方法,
而IdentityHashMap的key值的哈希值计算方式,调用的是System.identityHashCode(key)方法,返回的是地址的十进制数
集合类
Collection
List 列表
Set 集
Queue队列
Map散列 键值对
Set内部使用的是对应的Map来实现的
Map的key值即是存放Set的值,Key值对应的value存放的是一个固定值 static new object
HashSet ->HashMap
TreeSet->TreeMap
TreeSet
不可重复。Map中的key值不可以重复,可以是空,因此Set的值不可以重复可以是空。Set中的值是map的key.
TreeSet<String> set=new TreeSet<>();
set.add("str1");
set.add("str2");
//set.add(null);
set.add("str3");
set.add("str4");
//set.add(null);
Iterator<String> iterator=set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
set.remove("str3");
}
结果str1
str2
str3
str4
remove调用的map的remove方法
set的iterator调用的是map.key.iterator()
map的排序优先使用Comparator实现的方法比较key值,默认使用Comparable用自身比较传入的值。
故key值为空报空指针。
TreeSet的值不能为空。所以和TreeMap类似,TreeSet的元素必须实现Comparable接口,如果构造TreeSet指定了比较器Comparator,那么存放元素可以不用实现,则可以存放空。如果未指定时,add需要调用元素的默认比较器Comparable的compareTo方法,去比较元素的大小,如果存放空,则null.compareTo(T object)报错
TreeSet treeSet=new TreeSet(new Comparator(){…};);
HashSet
内部是HashMap来实现的,可以存放空值,但是只能有一个空值
contains()是否包含某个元素
EnumSet
内部使用的是一个枚举类的数组
HashMap的扩容
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
// 旧的数组的长度(原桶数)
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
// 数组已经初始化了,进行扩容操作
if (oldCap > 0) {
// 如果已经到达最大容量,则不再扩容
if (oldCap >= MAXIMUM_CAPACITY) {
// 阀值设置为最大 Integer 值
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 未到达最大容量
// 数组容量扩大为原来的2倍:newCap = oldCap << 1
// 阀值扩大为原来的2倍:newThr = oldThr << 1
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// 数组未初始化,且阀值大于0,此处阀值为什么大于0???
// 当构造 HashMap 时,如果传了容量参数,将根据容量参数计算的离它最近的2的次幂
// 即数组的容量暂存在阀值变量 threshold 中,详见构造器方法中的语句:
// this.threshold = tableSizeFor(initialCapacity);
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
// 数组未初始化且阀值为0,说明使用了默认构造方法进行创建对象,即 new HashMap()
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// newCap = oldThr; 语句之后未计算阀值,所以 newThr = 0
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// 根据新的容量创建一个数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 旧数组不为 null 时表示 resize 为扩容操作,否则为第一次初始化数组操作
if (oldTab != null) {
// 循环将数组中的每个结点并转移到新的数组中
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
// 获取头结点,如果不为空,说明该数组中存放有元素
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 头结点 e.next == null 时,表明链表或红黑树只有一个头结点
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 如果结点为红黑树结点,则红黑树分裂,转移到新表中
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// 否则为链表,将链表中各结点原序的转移至新表中
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// (e.hash & oldCap) == 0 时,链表所在桶的索引不变
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
// 否则将链表转移到索引为 index + oldCap 的桶中
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
// 返回新的数组
return newTab;
}
上述代码中,扩容操作使用了左移运算
newCap = oldCap << 1
newThr = oldThr << 1
数组容量和阀值均左移1位,表示原数乘以21,即扩容为原来的2倍。
当桶中存放的为链表,在进行链表的转移时,if判断使用了如下位操作
if ((e.hash & oldCap) == 0)
其中 oldCap 为扩容前数组的长度,为2的次幂,也即它的二进制中最高位为1,其余位都位0。而每次扩容为原来的2倍。
例如原容量为 16,即 oldCap = 10000,扩容后 newCap = 32,即 newCap = 100000。计算链表所在数组的索引表达式 hash & (length - 1):
扩容前,oldCap = 10000
length - 1 = oldCap - 1 = 1111
index = hash & (length - 1) = hash & 1111
数组索引下标 index 依赖于 hash 的低 4 位
扩容后,newCap = 100000
newLength - 1 = newCap - 1 = 11111
newIndex = hash & (newLength - 1) = hash & 11111
新数组索引下标 newIndex 依赖于 hash 的低 5 位
在上述例子中,扩容后,新的数组索引和原索引是否相等取决于 hash 的第5位,如果第5位为0,则新的数组索引和原索引相同;如果第5位为1,则新的数组索引和原索引不同。
如何测试 hash 第5位为0还是为1?因为 oldCap = 10000,刚好第5位为1,其余位都为0,因此 e.hash & oldCap 与操作的结果,hash 第5位为0时,结果为0,hash 第5位为1时,结果为1。
综上所述,扩容后,链表的拆分分两步:
一条链表不需要移动,保存在原索引的桶中,包含原链表中满足 e.hash & oldCap == 0 条件的结点
一条链表需要移动到索引为 index + oldCap 的桶中,包含原链表中不满足 e.hash & oldCap == 0 条件的结点