5、HashTable
5.1 先看看属性5.2 构造函数5.3 从put方法分析扩容机制
put方法addEntry方法(真正添加值的方法)rehash方法(扩容)扩容总结: 5.4 get方法5.5 remove方法5.6 replace5.7 HashTable和HashMap 6、HashSet
6.1 先看看属性6.2 构造方法6.3 add方法6.4 remove方法6.5 contains 7、linkedHashMap
7.1 先看看属性7.2 构造方法7.3 linkedHashMap完善的方法
1.afterNodeAccess方法2.afterNodeRemoval方法3.afterNodeInsertion方法
7.4 get方法
8、linkedHashSet
往期:
Collection集合工具类源码解读(一) — ArrayList 和 VectorCollection集合工具类源码解读(二) — linkedListCollection集合工具类源码解读(三) — HashMap 5、HashTable
先写个demo,debug
public static void main(String[] args) {
Hashtable
5.1 先看看属性
Hashtable和HashMap类似,他俩都实现了Map接口,但是hashtable继承自Dictionary,hashmap继承自AbstractMap
严格意义上来说应该是hashmap像hashtable,因为hashtable是jdk1.0出现的,hashmap是1.2出现的,hashmap是轻量级的hashtable
table:哈希表count:哈希表长度,容量threshold:扩容门槛loadFactor:加载因子 5.2 构造函数
四个构造函数:
Hashtable():无参构造,默认初始容量为11,加载因子为0.75,初始门槛为0.75*11=8(初始容量与hashmap不同)Hashtable(int initialCapacity):自定义容量,加载因子还是0.75Hashtable(int initialCapacity, float loadFactor):自定义容量,加载因子。扩容门槛为容量*加载因子Hashtable(Map extends K, ? extends V> t):传入一个map,根据map大小初始化,加载因子仍然是0.75
如果map的size比11大则初始化map两倍的容量否则就是初始化11的长度 5.3 从put方法分析扩容机制 put方法
进入put方法
- 首先判断value(实际上key也不能为null,在hashcode方法里如果key为null,就会空指针)是否为空,为空就抛出空指针异常(所以hashtable键和值都不能为空)然后拿到原来的table,用hash计算下标,用index计算下标,这里和0x7FFFFFFF(32位整数最大位)相与就是限定位数的,超过部分与0就是0。最后用table[index]拿到当前索引位置先看看当前table[i]有不有值,有就遍历找key相等的,然后替换这个值table[i]为空,就直接addEntry进去
如图:addEntry方法
- 判断之前的count是否到达扩容门槛,到达了就rehash扩容
- hash计算最新的index
如图为rehash方法
- 拿到旧容量和旧table计算新容量:左移一位再加一:old * 2 +1 ,然后再判断是否超过范围计算新门槛:新容量*加载因子 ,然后再判断是否超过范围把旧元素移到新的table里
扩容总结:
hashtable 默认容量为11,默认加载因子为0.75,所以第一次扩容门槛为11*0.75=8扩容时:在添加当前要添加的数之前执行。判断之前的count是否大于等于当前的容量
如果大于等于就要扩容 扩容机制:newCapacity = oldCapacity * 2 + 1 5.4 get方法
首先注意到get方法加了synchronized,实际上,hashtable所有方法都加了synchronized
实际上方法里面的思想和hashmap是一样的
get方法传递一个key,返回查到的值,如果查不到返回nullgetOrDefault,传递一个key,defaultValue,查到返回查到的值,查不到返回默认值 5.5 remove方法
同样的加了synchronized,思想是先计算hash对应的table[i],再在这条链表上遍历找到要删除的,按照链表的方式删除
5.6 replace可以看到,先判空,然后找到原来的值,更换新的值,和hashmap是大相径庭的
5.7 HashTable和HashMapHashMap不是线程安全的,HashTable由于方法加了synchronized是线程安全,效率上可能hashmap高(差别在锁的消耗)
Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差 HashMap允许null key和null value,而hashtable不允许null key和null valueHashMap继承AbstractMap类,Hashtable继承自Dictionary类初始容量不同:hashmap初始为16,hashtable初始为11 6、HashSet
先写个demo,debug
public class HashSetDemo {
public static void main(String[] args) {
HashSet
6.1 先看看属性
HashSet里面内嵌一个HashMap,还有个常量PRESENT为空对象
HashSet添加的元素作为hashMap的key,这个PRESENT空对象作为hashmap的value
6.2 构造方法hashSet构造方法完全使用的是hashMap,没什么可说的
6.3 add方法HashSet就是调用的hashmap的put方法
将添加的元素作为hashMap的key,PRESENT空对象作为hashmap的value
所以为什么set能排重,就是这个原因:
hashmap用key计算hash,每次key相同就替换调原来的值所以用set插入相同的两个元素,两个元素在hashmap里面都是key:PRESENT无论怎么替换key只有一个,所以能够排重 6.4 remove方法
又是调用的hashmap的remove方法,hashmap的remove传入一个Object,如果找到了就返回这个Object并删除,如果不存在就返回null
6.5 containshashSet的contains方法是对hashmap的containsKey的封装
7、linkedHashMap 7.1 先看看属性linkedHashMap是继承自HashMap的,他对HashMap的Entry进行了修改,多了一个before和after用来互相连接,同时这个Entry有head和tail头和尾
简单来说:linkedHashMap维护了一个双向链表
注意注意注意:这个类由于是继承HashMap,所有方法基本上都是稍微重写了一下HashMap的
还有一点:accessOrder属性:
false(默认),基于插入顺序:顺序是插入是固定的true:基于访问顺序:每次访问会调用afterNodeAccess方法将其移到末尾 7.2 构造方法
就是调用HashMap的构造方法,没什么好说的
7.3 linkedHashMap完善的方法在HashMap里面,留了三个空方法,就是给linkedHashMap使用的
1.afterNodeAccess方法当结点加入HashMap后一并加入linkedHashMap里面的双向链表后
2.afterNodeRemoval方法当删除结点时也从双向链表中移除
3.afterNodeInsertion方法如果传入的值为true,就删除链表头结点
7.4 get方法这个get方法,linkedHashMap对其进行了重写,主要是对accessOrder = true的情况。基于访问顺序,每次查看都会把查看过的结点放到末尾
8、linkedHashSetlinkedHashSet代码一共就200行
关键代码是调用了HashSet里面那个default修饰的构造函数
实际上底层仍然是linkedHashMap,所以就不过多赘述了



