4-1 HashMap源码(2021-11-9)
1 Map接口和常用方法(jdk1.8)
-
用于保存具有映射关系数据key-value双列元素。
-
Map中的key和value可以是任何类型元素,会封装到HashMap$Node对象中。
-
Map中的key不允许重复,有相同的key时,就替换。
-
Map中的key可以为null,value也可以为null;但是key只允许有一个null,value可以有多个。
-
常用key作key,但是其他对象也可以。
-
Node实现了Entry接口。
2 Map遍历方式
//第一组:先 取出 所有 key ,通过 Key 取出对应的Value。
//最简单:
System.out.println("============第一组============");
Set keyset = map.keySet();
for(Object key : keyset){//增强for
System.out.println(key + "-" + map.get(key));
}
//迭代器
System.out.println("============第一组============");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组:把所有的value取出。
System.out.println("============第二组============");
Collection values = map.values();
//增强for
for(Object value : values){
System.out.println(value);
}
//迭代器
System.out.println("============第二组============");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组:通过EntrySet 来获取 k-v。
System.out.println("============第三组============");
Set entrySet = map.entrySet();
for(Object entry : entrySet){
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
System.out.println("============第三组============");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next(); // Node
Map.Entry m = (Map.Entry) entry;//向下转型
System.out.println(m.getKey() + "-" + m.getValue());
}
3 put方法(HashSet分析过源码)
-
无参构造:初始化加载因子0.75;
-
put一个元素:
-
putVal(传入key的hash值,key,value)
-
如果table是null 或者 长度 = 0 就扩容,第一次扩为16,阈值是12.
-
如果第一处是null,直接加入。
-
else第一处不是null:
- 如果hash值相同 并且 ( 是相同对象 || equals 相同 ):e=p。
- else if :判断是不是树,如果是,按照红黑树的方法添加。(此处还没有深究)
- else:后面跟的是链表:
- 循环对比:
- 如果下一处是null,就加入;再判断这条链表是否需要树化;break;
- 如果hash值相同 并且 ( 是相同对象 || equals 相同 );break;
- 循环对比:
- 如果e不是null:
- 替换。
-
++modCount;
-
if (++size > threshold) //判断是否到达阈值 resize(); //扩容 afterNodeInsertion(evict); return null; //执行成功!
-
-
4 resize方法
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold; // 确定阈值 12
int newCap, newThr = 0;
if (oldCap > 0) { // 如果 oldCap > 0
if (oldCap >= MAXIMUM_CAPACITY) { // >= 2乘1073741824 ?
threshold = Integer.MAX_VALUE; // table最大值
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && // ( newCap = oldCap*2 < 2乘1073741824) && (oldCap >= 16)
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // 新的阈值 = 旧的阈值 * 2
}
else if (oldThr > 0) // 啥时 候进来??
newCap = oldThr; // 新容量 = 旧的阈值
else { // 0容量时,进入。
newCap = DEFAULT_INITIAL_CAPACITY; // 16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 12
}
if (newThr == 0) { // 新的阈值 = 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; //
if (oldTab != null) { // oldTab != null
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = 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;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
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; // 返回更新后的 table。
}
5 hash计算
- 得到hash值——>得到索引值。
static final int hash(Object key) { //hash方法(key)
int h;
return (key == null) ? 0 : ( h = key.hashCode() ) ^ ( h >>> 16 ); //求出hashCode值。
}
// Object.java 中
public native int hashCode(); // 不知道
5-1 为什么h = key.hashCode()) 与 (h >>> 16) 异或?
- 在jdk1.7中有indexFor(int h, int length)方法。jdk1.8里没有,但原理没变。1.8中用**tab[(n - 1) & hash]**代替但原理一样。
- 1.7源码:
static int indexFor(int h, int length) { // jdk1.7 得到下标方法。
return h & (length-1); //返回的是 数组下表 = 索引值。
}
-
原因总结:
- 由于和(length-1)运算,length 绝大多数情况小于2的16次方。所以始终是hashcode 的低16位(甚至更低)参与运算。要是高16位也参与运算,会让得到的下标更加散列。
- 所以这样高16位是用不到的,如何让高16也参与运算呢?所以才有**hash(Object key)**方法。让他的hashCode()和自己的高16位^运算。
-
例子:
- 补充:
当length=8时 下标运算结果取决于哈希值的低三位。
当length=16时 下标运算结果取决于哈希值的低四位。
当length=32时 下标运算结果取决于哈希值的低五位。
当length=2的N次方, 下标运算结果取决于哈希值的低N位。
6 remove方法
public V remove(Object key) { //按照key 删除元素
Node<K,V> e; // 创建新的结点
return (e = removeNode( hash(key) , key, null, false, true) ) == null ? null : e.value;
// 没有这个key就返回null
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) { //传入要删除的元素信息。
Node<K,V>[] tab; //临时数组
Node<K,V> p; //临时Node元素
int n, index; // 临时变量
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) { // 如果数组有内容 并且 长度大于0 并且 要删除的key位置有内容
Node<K,V> node = null, e; //临时Node
K k;
V v;
if (p.hash == hash && //如果第一个就找到了!!!node = p
((k = p.key) == key || (key != null && key.equals(k))))
node = p; //第一个就找到了!
else if ((e = p.next) != null) { // else:
if (p instanceof TreeNode) //是红黑树就这样:
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else { //是链表:
do { //循环:
if (e.hash == hash && // 找到到相同的了
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e; //node
break; // 退出循环
}
p = e; //后移。
} while ((e = e.next) != null);//循环到尾巴!
}
}
// 找到了node &&
if (node != null && (!matchValue || (v = node.value) == value||(value != null && value.equals(v)))) {
if (node instanceof TreeNode) // 红黑树 去元素。
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p) // 不是红黑树,=第一处元素。
tab[index] = node.next; // 第一处置空给。
else // != 第一处元素
p.next = node.next; // 前->后,中间断。
++modCount;
--size; // size-1。
afterNodeRemoval(node);
return node; // 返回 node
}
}
return null;
}
7 get方法
public V get(Object key) { // 传入key
Node<K,V> e; // 临时Node
return (e = getNode( hash(key), key ) ) == null ? null : e.value; // 没找到就返回null ;找到了就返回 值。
}
// getNode( hash(key), key ) 返回的永远是一个对象(找不到就是null)。
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; //临时 table
Node<K,V> first, e; //临时Node
int n; K k; // 临时变量
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
//有table && 长度>0 && 第一处有元素
if (first.hash == hash && // 检查第一处元素 是否相同
((k = first.key) == key || (key != null && key.equals(k))))
return first; // 一次查看相同返回 first(Node)!!!!!幸运!
if ((e = first.next) != null) { //如果尾巴还有,向下寻找!
if (first instanceof TreeNode) // 如果是红黑树按照树的方式寻找。
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do { // 跟着的是链表:循环查找。
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e; // 碰到就返回。
} while ((e = e.next) != null); // 直到循环到最后一个(链表尾巴)。
}
}
return null;
}