写点什么

HashMap 源码解析,操作系统原理与实践教程第三版答案

用户头像
极客good
关注
发布于: 刚刚

*/


public HashMap() {


this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted


}


/**


  • Constructs a new <tt>HashMap</tt> with the same mappings as the

  • specified <tt>Map</tt>. The <tt>HashMap</tt> is created with

  • default load factor (0.75) and an initial capacity sufficient to

  • hold the mappings in the specified <tt>Map</tt>.

  • @param m the map whose mappings are to be placed in this map

  • @throws NullPointerException if the specified map is null


*/


public HashMap(Map<? extends K, ? extends V> m) {


this.loadFactor = DEFAULT_LOAD_FACTOR;


putMapEntries(m, false);


}


复制代码

查询

/**


  • 返回指定 key 所对应的 value 值,当不存在指定的 key 时,返回 null。

  • 当返回 null 的时候并不表明哈希表中不存在这种关系的映射,有可能对于指定的 key,其对应的值就是 null。

  • 因此可以通过 containsKey 来区分这两种情况。


*/


public V get(Object key) {


Node<K,V> e;


return (e = getNode(hash(key), key)) == null ? null : e.value;


}


/**


  • 1.首先通过 key 的哈希值找到其所在的哈希桶

  • 2.对于 key 所在的哈希桶只有一个元素,此时就是 key 对应的节点,

  • 3.对于 key 所在的哈希桶超过一个节点,此时分两种情况:

  • 4.查找不到相应的 key,返回 null


*/


final Node<K,V> getNode(int hash, Object key) {


Node<K,V>[] tab; Node<K,V> first, e; int n; K k;


if ((tab = table) != null && (n = tab.length) > 0 &&


(first = tab[(n - 1) & hash]) != null) {


if (first.hash == hash && // always check first node


((k = first.key) == key || (key != null && key.equals(k))))


return first;


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;


}


复制代码

存储

/**


  • 在映射中,将指定的键与指定的值相关联。如果映射关系之前已经有指定的键,那么旧值就会被替换


*/


public V put(K key, V value) {


return putVal(hash(key), key, value, false, true);


}


/**


  • @param onlyIfAbsent if true, don't change existing value

  • 1.判断哈希表 table 是否为空,是的话进行扩容操作

  • 2.根据键 key 计算得到的 哈希桶数组索引,如果 table[i] 为空,那么直接新建节点

  • 3.判断 table[i] 的首个元素是否等于 key,如果是的话就更新旧的 value 值

  • 4.判断 table[i] 是否为 TreeNode,是的话即为红黑树,直接在树中进行插入

  • 5.遍历 table[i],遍历过程发现 key 已经存在,更新旧的 value 值,否则进行插入操作,插入后发现链表长度大于 8,则将链表转换为红黑树


*/


final V putVal(int hash, K key, V value, boolean onlyIfAbsent,


boolean evict) {


Node<K,V>[] tab; Node<K,V> p; int n, i;


//哈希表 table 为空,进行扩容操作


if ((tab = table) == null || (n = tab.length) == 0)


n = (tab = resize()).length;


// tab[i] 为空,直接新建节点


if ((p = tab[i = (n - 1) & hash]) == null)


tab[i] = newNode(hash, key, value, null);


else {


Node<K,V> e; K k;


//tab[i] 首个元素即为 key,更新旧值


if (p.hash == hash &&


((k = p.key) == key || (key != null && key.equals(k))))


e = p;


//当前节点为 TreeNode,在红黑树中进行插入


else if (p instanceof TreeNode)


e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);


else {


//遍历 tab[i],key 已经存在,更新旧的 value 值,否则进心插入操作,插入后链表长度大于 8,将链表转换为红黑树


for (int binCount = 0; ; ++binCount) {


if ((e = p.next) == null) {


p.next = newNode(hash, key, value, null);


//链表长度大于 8


if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st


treeifyBin(tab, hash);


break;


}


// key 已经存在


if (e.hash == hash &&


((k = e.key) == key || (key != null && key.equals(k))))


break;


p = e;


}


}


//key 已经存在,更新旧值


if (e != null) { // existing mapping for key


V oldValue = e.value;


if (!onlyIfAbsent || oldValue == null)


e.value = value;


afterNodeAccess(e);


return oldValue;


}


}


//HashMap 插入元素表明进行了结构性调整


++modCount;


//实际键值对数量超过 threshold,进行扩容操作


if (++size > threshold)


resize();


afterNodeInsertion(evict);


return null;


}


复制代码

扩容

/**


  • 初始化或者对哈希表进行扩容操作。如果当前哈希表为空,则根据字段阈值中的初始容量进行分配。

  • 否则,因为我们扩容两倍,那么对于桶中的元素要么在原位置,要么在原位置再移动 2 次幂的位置。


*/


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) {


threshold = Integer.MAX_VALUE;


return oldTab;


}


//容量没有超过最大值,容量变为原来两倍


else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&


oldCap >= DEFAULT_INITIAL_CAPACITY)


//阈值变为原来两倍


newThr = oldThr << 1; // double threshold


}


else if (oldThr > 0) // initial capacity was placed in threshold


//调用了 HashMap 的带参构造器,初始容量用 threshold 替换,


//在带参构造器中,threshold 的值为 tableSizeFor() 的返回值,也就是 2 的幂次方,而不是 capacity * load factor


newCap = oldThr;


else { // zero initial threshold signifies using defaults


//初次初始化,容量和阈值使用默认值


newCap = DEFAULT_INITIAL_CAPACITY;


newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);


}


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;


if (oldTab != null) {


for (int j = 0; j < oldCap; ++j) {


Node<K,V> e;


if ((e = oldTab[j]) != null) {


//将原哈希桶置空,以便 GC


oldTab[j] = null;


//当前节点不是以链表形式存在,直接计算其应放置的新位置


if (e.next == null)


newTab[e.hash & (newCap - 1)] = e;


//当前节点是 TreeNode


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;


}


//原索引 + 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;


}


复制代码


因为哈希表使用 2 次幂的拓展(指长度拓展为原来的 2 倍),所以在扩容的时候,元素的位置要么在原位置,要么在原位置再移动 2 次幂的位置。为什么是这么一个规律呢?我们假设 n 为 table 的长度,图(a)表示扩容前的 key1 和 key2 两种 key 确定索引位置的示例,图(b)表示扩容后 key1 和 key2 两种 key 确定索引位置的示例,其中 hash1 是 key1 对应的哈希与高位运算结果。



元素在重新计算 hash 之后,因为 n 变为 2 倍,那么 n-1 的 mask 范围在高位多 1bit(红色),因此新的 index 就会发生这样的变化:



因此,我们在扩容的时候,只需要看看原来的 hash 值新增的那个 bit 是 1 还是 0 就好了,是 0 的话索引没变,是 1 的话索引变成“原索引+oldCap”,可以看看下图为 16 扩充为 32 的 resize 示意图:


删除

/**


  • 删除指定的 key 的映射关系


*/


public V remove(Object key) {


Node<K,V> e;


return (e = removeNode(hash(key), key, null, false, true)) == null ?


null : e.value;


}


/**Java


  • 1.根据 key 的哈希值在哈希桶中查找是否存在这个包含有这个 key 的节点

  • 2.如果查找到对应的节点,进行删除操作

  • @param hash key 的 hash 值

  • @param key 指定的 key

  • @param value 当 matchhValue 为真时,则要匹配这个 value

  • @param matchValue 为真并且与 value 相等时进行删除

  • @param movable if false do not move other nodes while removing

  • @return the node, or null if none


*/


final Node<K,V> removeNode(int hash, Object key, Object value,


boolean matchValue, boolean movable) {


Node<K,V>[] tab; Node<K,V> p; int n, index;


if ((tab = ta


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


ble) != null && (n = tab.length) > 0 &&


(p = tab[index = (n - 1) & hash]) != null) {


Node<K,V> node = null, e; K k; V v;


//链表头即为要删除的节点


if (p.hash == hash &&


((k = p.key) == key || (key != null && key.equals(k))))


node = p;


else if ((e = p.next) != null) {


//节点为 TreeNode,在红黑树中查找是否存在指定的 key


if (p instanceof TreeNode)


node = ((TreeNode<K,V>)p).getTreeNode(hash, key);


else {


//在链表中查找是否存在指定的 key


do {


if (e.hash == hash &&


((k = e.key) == key ||


(key != null && key.equals(k)))) {


node = e;


break;


}


p = e;


} while ((e = e.next) != null);


}


}


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);

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
HashMap源码解析,操作系统原理与实践教程第三版答案