java-HashMap源码学习
阅读提示:HashMap源码在不同版本情况下,具体源码可能不一样(优化问题),但功能几乎是相同的(博主1.8)
什么是Hash?
hash表是一种数据结构,它拥有惊人的效率,它的时间复杂度低到接近O(1)这样的常数级。
hash表的实现主要是:
1.计算存储位置的hash函数。
2.处理哈希冲突的方法。
3.hash的物理存储。
hash函数
它的目的是通过一个key选出(映射)一个唯一的存储地址。
最常见的hash函数:f(key)=a*key+b
这里a,b为常数(不为0),f(key)就是计算出的哈希值
一般一个hash函数的设计好坏,直接影响到效率。
哈希冲突
hash冲突定义:当两个key相同时计算的hash值会一样,导致冲突。
hash冲突解决部分方法:
**开放定地址:**找下一块地址(另一个hash表(另一个未用过的hash值·),无限多)
**再散列函数法:**一个hash函数解决不了,就两个,两个不行就三个…
**链地址法(hash桶(此处链表)的概念):**数组+链表,将具有相同hash的同义词按次序放入链表,并链表存在数组。链表的hash值以数组hash值开始确定新的hash(不担心与其它数组的hash相同)
还有的就不做介绍了
物理存储
物理存储结构:顺序|链式存储
hash表的主干永远都是一个数组
一般的hash只需要一个数组来存储,一般以数组下标做hash值。
在链地址法中需要用到链表。
什么是Map?
Map在计算机概念是一个key-value(唯一性)键值对
什么是HashMap?
根据前面的铺垫,
hashMap的存储主干是一个数组(源码中的Node(有些是Entry)对象数组),
Node(Entry)对象包含了Key-Value属性
hashMap处理hash冲突:java1.8后,使用的是链地址法。
数据存储结构如下:
在拥有链表的情况下,hashMap的查询效率必然是低一些的,复杂度提高到o(n)
但1.8利用了红黑树数据结构,又将复杂度降为了o(Log(n))
HashMap的源码分析
1.构造函数(4个)
部分成员变量:
///
transient 关键字:再被修饰后变量序列化将不可见
///
threshold:初始空间(initialCapacity传入参数)
loadFactor:负载因子
modCount:是快速失败的判断标准(在迭代时,其他线程访问Map,并导致其结构改变,会抛异常)
table:节点Set集合(HashMap主干,是个数组)
initialCapacity默认为16,loadFactory默认为0.75
负载因子在0.75时效率最好(数学统计学验证),用于衡量空间利用的方法选择
hashMap会扩容且容量永远是2的幂
合理判断参数,就结束构造public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor = loadFactor;this.threshold = tableSizeFor(initialCapacity);}合理判断空间大小参数,使用默认负载参数就结束构造public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}/使用默认参数就结束构造public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}构造一个具有相同的映射关系与指定Map一个新的HashMap。 HashMap中与默认负载因数(0.75)和初始容量足以容纳在指定的地图的映射创建public HashMap(Map<? extends K, ? extends V> m) {this.loadFactor = DEFAULT_LOAD_FACTOR;putMapEntries(m, false);}
发现此时table变量未分配空间。根据put函数源码发现,table变量在put()分配空间
再看关键对象Node(Entry)
//Map.Entry<K,V>是一个接口//Nodestatic class Node<K,V> implements Map.Entry<K,V> {final int hash;//哈希值final K key;//键值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;}//other}
再看其部分关键函数
hash() : key.hashCode()产生依靠equals()方法,位移异或等操作的哈希值
,这个根据版本不同也计算方法不同,但是hashCode()几乎一直存在
put() : 计算hash值然后对table
/*单位计算key.hashCode()和差(异或)的散列以降低的较高位。因为表使用功率的两掩蔽,套散列的,只有在当前的掩模将总是碰撞上述位变化。 (其中著名的例子是一组浮动键的小桌子控股连续整数)。所以我们施加向下变换利差较高位的影响。 有速度,实用,和位传播质量之间的权衡。 由于哈希许多共同的集已合理分配(所以不要蔓延受益),因为我们用树来处理大型成套碰撞的垃圾箱,我们只是XOR一些最便宜的方式转移位降低系统lossage,以及纳入,否则将永远不会因为表界的指数计算中使用的最高位的影响。*/static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}/*** Implements Map.put and related methods** @param hash hash for key* @param key the key* @param value the value to put* @param onlyIfAbsent if true, don't change existing value* @param evict if false, the table is in creation mode.* @return previous value, or null if none*/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;}
JDK1.8在JDK1.7的基础上针对增加了红黑树来进行优化。即当链表超过8时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。
看上面的put函数的putVal()就含有红黑树的插入方法,
一开始普通的链表不需要红黑树。
下列源码可以看出,
转换红黑树条件是
链表(是在每次遍历到数组时每个单元对于的链表,参考上图)节点数大于等于8且数组长度大于等于64,
详见方法: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);}}
//红黑树构造类
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent; // red-black tree linksTreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev; // needed to unlink next upon deletionboolean red;//是红节点吗TreeNode(int hash, K key, V val, Node<K,V> next) {super(hash, key, val, next);}}
HashMap源码探究到此为止,以后可以继续深入,学习红黑树的实际应用等等
在此附上一张网上Copy过来的图:
是讲1.8版本的HashMap在增加元素后一系列的操作步骤,以及优化方式流程图
集合源码魅力无穷。
踏踏实实读源码。
java-HashMap源码学习相关推荐
- JAVA JDK 源码学习
JAVA JDK 源码学习 ,以1.8为例,按照下面图片顺序依次学习: applet ,awt,beans,io,lang,math,net,nio,rmi,security,sql,text,tim ...
- Java集合源码学习(四)HashMap
一.数组.链表和哈希表结构 数据结构中有数组和链表来实现对数据的存储,这两者有不同的应用场景, 数组的特点是:寻址容易,插入和删除困难:链表的特点是:寻址困难,插入和删除容易: 哈希表的实现结合了这两 ...
- 搞懂 Java HashMap 源码
HashMap 源码分析 前几篇分析了 ArrayList , LinkedList ,Vector ,Stack List 集合的源码,Java 容器除了包含 List 集合外还包含着 Set 和 ...
- HashMap源码学习——初探
写这篇文章前,首先感谢MrBird的(https://mrbird.cc/Java-HashMap底层实现原理.html)这篇文章,里面讲解的HashMap源码内容非常棒,本文仅作为自身学习的记录,如 ...
- java Integer 源码学习
转载自http://www.hollischuang.com/archives/1058 Integer 类在对象中包装了一个基本类型 int 的值.Integer 类型的对象包含一个 int 类型的 ...
- Java HashMap源码剖析
一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap ...
- Java集合源码学习(五)几种常用集合类的比较
这篇笔记对几个常用的集合实现,从效率,线程安全和应用场景进行综合比较. 1.ArrayList.LinkedList与Vector的对比 (1)相同和不同 都实现了List接口,使用类似. Vecto ...
- Java集合框架之 Java HashMap 源码解析
继上一篇文章Java集合框架综述后,今天正式开始分析具体集合类的代码,首先以既熟悉又陌生的HashMap开始. 签名(signature) public class HashMap<K,V> ...
- Java集合源码学习(4)HashSet
1 概述 HashSet 是一个没有重复元素的集合.它的底层是由HashMap实现的,不保证元素的顺序.HashSet允许使用 null 元素. 截取一段源码: public class HashSe ...
- HashMap 源码学习
签名(signature) public class HashMap<K,V>extends AbstractMap<K,V>implements Map<K,V> ...
最新文章
- 使用Python制作酷炫的二维码
- 北京冬奥一项AI黑科技即将走进大众:实时动捕三维姿态,误差不到5毫米
- 操作 神通数据库_国产数据库最好的时代
- mysql流程控制_Mysql之流程控制结构
- 手把手教你把代码丢入github 中
- python类和对象_Python类和对象
- 使用电脑时,眼睛离电脑多远才合适
- 解决Please define the NDK_PROJECT_PATH variable to point to it.
- iOS 常用第三方开源框架介绍
- 详解TCP的四报文挥手
- 电路杂谈——硬件经典面试题
- 51CTO学院三周年-51cto学院伴我成长
- 交换机软件测试,交换机性能测试方法
- Ubuntu 18.04配置静态IP地址
- 如何制作马赛克是硬质纤维板应该正确基金会对于马赛克
- 第五章-系统的频域分析
- java 压缩图片至指定大小
- QQ邮箱IMAP/SMTP服务,设置 未成功原因
- php下拉框css样式,纯CSS实现的下拉菜单
- Python jieba分词如何添加自定义词和去除不需要长尾词