当前位置: 首页 > news >正文

java中hashmap源码解析(jdk1.8)

文章目录

  • HashMap的构造与相关参数
    • ①、resize()方法
    • ②、putVal()方法
    • ③、get方法
    • ④、remove

HashMap的构造与相关参数

HashMap是一个用来存放键值对,基于哈希表的Map接口实现,为常用的Java集合,非线程安全。

HashMap它底层存储的数据结构,为数组 + 链表,而之所以采用数组,是因为数组可以随机访问,通过hash将key的值和我们的最大容量进行取余就可以获得坐标,这样存取更加方便,将数组每个坐标看作桶,而链表的使用是为了解决哈希冲突的问题,如果碰到冲突,就在桶下用链表连接,即“拉链法”。

在这里插入图片描述

而随着插入数据的增多,超过一定阈值之后,数组和链表长度都越来越大之后,我们查找的效率就会越来越低,因此,在jdk1.8之后,引入了红黑树的实现,来解决哈希冲突。

在这里插入图片描述
当链表长度大于阈值(默认为8)时,执行 treeifyBin() 方法:

final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)resize();else if ((e = tab[index = (n - 1) & hash]) != null) {TreeNode<K,V> hd = null, tl = null;do {TreeNode<K,V> p = replacementTreeNode(e, null);if (tl == null)hd = p;else {p.prev = tl;tl.next = p;}tl = p;} while ((e = e.next) != null);if ((tab[index] = hd) != null)hd.treeify(tab);}}

解释一下这段代码,当数组为空或者数组长度小于阈值(MIN_TREEIFY_CAPACITY)时,就继续执行resize(),否则就将链表转为红黑树。

//数组长度阈值,要牢记
static final int MIN_TREEIFY_CAPACITY = 64;

类的构造参数:

public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {//序列号private static final long serialVersionUID = 362498820763181265L;// 默认初始容量为16static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16// 最大容量static final int MAXIMUM_CAPACITY = 1 << 30;// 默认的负载因子static final float DEFAULT_LOAD_FACTOR = 0.75f;// 链表转红黑树的链表长度阈值static final int TREEIFY_THRESHOLD = 8;// 红黑树转链表的阈值static final int UNTREEIFY_THRESHOLD = 6;// 链表转红黑树对应的桶长度阈值static final int MIN_TREEIFY_CAPACITY = 64;// 存储元素的数组,总是2的幂次倍transient Node<k,v>[] table;// 存放元素的个数,注意这个不等于数组的长度。transient int size;// 每次扩容和更改map结构的计数器transient int modCount;// 阈值(容量*负载因子) 当实际大小超过阈值时,会进行扩容int threshold;// 负载因子final float loadFactor;

我们可以发现,存储传入的kv键值对我们是使用一个Node类来存储,结构如下:

static class Node<K,V> implements Map.Entry<K,V> {//记录哈希值final int hash;// key值final K key;// value值V value;// 链表指针Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}public final K getKey()        { return key; }public final V getValue()      { return value; }public final String toString() { return key + "=" + value; }public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (o == this)return true;if (o instanceof Map.Entry) {Map.Entry<?,?> e = (Map.Entry<?,?>)o;if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}}

①、resize()方法

  • 每次执行resize,我们首先需要先获得当前数组的长度和阈值。
  • oldThr为扩容触发条件,初始为 capacity × loadFactor。
  • oldTable为旧表,其容量为oldCap。
  • 然后oldCap如果在 230 - 24 之间,就继续扩容,每次扩容都是乘2
  • 随后通过newThr来判断给新数组的赋值情况
  • 之后创建新数组newTab,将旧表中的元素转移到新表(这个过程相对耗时
  • 最后更新一下threshold,确保下次扩容触发条件正确,并返回newTab,扩容成功。
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 thresholdnewCap = oldThr;else {               // zero initial threshold signifies using defaultsnewCap = 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) {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 orderNode<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;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.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;}

②、putVal()方法

  • (n - 1) & hash这个操作,因为n即数组的长度是2n 因此,n - 1之后,它的后n位就都是1了,这样hash & (n - 1)通过一次操作就实现了取余功能。

  • 随后插入的话,通过这种哈希方法我们都会找到相应的桶,然后获取并遍历每个桶的node结点,碰到相同的就用新的值覆盖,为空就new一个新node插入。

  • 同时每次插入后都会进行一次判断是否达到了数组扩容阈值的操作,如果达到了就进行扩容,否则不做处理。

  • 如果达到了转红黑树的条件,就由链表转红黑树。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;}

③、get方法

  • 简单来说,get的逻辑就是手下获取当前数组和hash值,随后对对应下标的链表进行遍历查询,之后判断有没有我们想要取得值,取到就返回,否则就返回null。
public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;
}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;
}

④、remove

  • 流程与get()差不多,首先获取下标,随后遍历查找要删除的目标,如果找到的是桶节点第一个,就直接更新桶 tab[index] = node.next,如果是链表中间结点,更新前一个节点的 next 指针:p.next = node.next。
public V remove(Object key) {Node<K,V> e;return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;
}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 = table) != 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) {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;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);else if (node == p)tab[index] = node.next;elsep.next = node.next;++modCount;--size;afterNodeRemoval(node);return node;}}return null;
}
http://www.xdnf.cn/news/306163.html

相关文章:

  • 代码mark:脚本获取包含全角字符的字符串的长度
  • php中serialize和unserialize的用法详解
  • 开源模型应用落地-qwen模型小试-Qwen3-8B-推理加速-vLLM-Docker(二)
  • 鸿蒙NEXT开发动画(风格的弹性缩放加载动画组件)
  • 长实公布新盘案名“花语海” 打造全新“维港都会公园圈”
  • Dubbo(99)如何在区块链系统中应用Dubbo?
  • RLOO:将多次其他回答的平均reward作为baseline
  • [250505] Arch Linux 正式登陆 Linux 的 Windows 子系统
  • 电动金属硬密封蝶阀泄露等级:水、蒸汽、油品介质的零泄漏守护方案-耀圣
  • Relay 算子调用流程
  • Java 函数式编程
  • 高斯计校准的重要性
  • 【C语言】推箱子小游戏
  • 初步认识java
  • 精益数据分析(42/126):移动应用商业模式的深度剖析与实战要点
  • 浏览器存储 Cookie,Local Storage和Session Storage
  • 在 Sheel 中运行 Spark:开启高效数据处理之旅
  • 公司项目架构搭建者
  • LXwhat-嘉立创
  • 5G+教育:如何重塑未来课堂?
  • 打造智慧养老实训室,构建科技赋能养老新生态
  • 精益数据分析(44/126):深度解析媒体网站商业模式的关键要点
  • 安装篇--CentOS 7 虚拟机安装
  • 【AI】用AI将文档、文字一键生成PPT的方法(百度的自由画布版)
  • OpenCV 图形API(79)图像与通道拼接函数-----将一个三通道的 GMat 图像拆分为三个单独的单通道 GMat函数split3()
  • Coding Practice,48天强训(29)
  • MySQL8查询某个JSON类型的字段中出现过的所有键名(json key name)并去重返回
  • CKESC ROCK 280A-M 电调专业测评:工业级性能与智能保护的深度平衡
  • 如何从服务器日志中分析是否被黑客攻击?
  • 多线程系列五:面试中常考的单例模式