阅读提示: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源码学习相关推荐

  1. JAVA JDK 源码学习

    JAVA JDK 源码学习 ,以1.8为例,按照下面图片顺序依次学习: applet ,awt,beans,io,lang,math,net,nio,rmi,security,sql,text,tim ...

  2. Java集合源码学习(四)HashMap

    一.数组.链表和哈希表结构 数据结构中有数组和链表来实现对数据的存储,这两者有不同的应用场景, 数组的特点是:寻址容易,插入和删除困难:链表的特点是:寻址困难,插入和删除容易: 哈希表的实现结合了这两 ...

  3. 搞懂 Java HashMap 源码

    HashMap 源码分析 前几篇分析了 ArrayList , LinkedList ,Vector ,Stack List 集合的源码,Java 容器除了包含 List 集合外还包含着 Set 和 ...

  4. HashMap源码学习——初探

    写这篇文章前,首先感谢MrBird的(https://mrbird.cc/Java-HashMap底层实现原理.html)这篇文章,里面讲解的HashMap源码内容非常棒,本文仅作为自身学习的记录,如 ...

  5. java Integer 源码学习

    转载自http://www.hollischuang.com/archives/1058 Integer 类在对象中包装了一个基本类型 int 的值.Integer 类型的对象包含一个 int 类型的 ...

  6. Java HashMap源码剖析

    一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap  ...

  7. Java集合源码学习(五)几种常用集合类的比较

    这篇笔记对几个常用的集合实现,从效率,线程安全和应用场景进行综合比较. 1.ArrayList.LinkedList与Vector的对比 (1)相同和不同 都实现了List接口,使用类似. Vecto ...

  8. Java集合框架之 Java HashMap 源码解析

    继上一篇文章Java集合框架综述后,今天正式开始分析具体集合类的代码,首先以既熟悉又陌生的HashMap开始. 签名(signature) public class HashMap<K,V> ...

  9. Java集合源码学习(4)HashSet

    1 概述 HashSet 是一个没有重复元素的集合.它的底层是由HashMap实现的,不保证元素的顺序.HashSet允许使用 null 元素. 截取一段源码: public class HashSe ...

  10. HashMap 源码学习

    签名(signature) public class HashMap<K,V>extends AbstractMap<K,V>implements Map<K,V> ...

最新文章

  1. 使用Python制作酷炫的二维码
  2. 北京冬奥一项AI黑科技即将走进大众:实时动捕三维姿态,误差不到5毫米
  3. 操作 神通数据库_国产数据库最好的时代
  4. mysql流程控制_Mysql之流程控制结构
  5. 手把手教你把代码丢入github 中
  6. python类和对象_Python类和对象
  7. 使用电脑时,眼睛离电脑多远才合适
  8. 解决Please define the NDK_PROJECT_PATH variable to point to it.
  9. iOS 常用第三方开源框架介绍
  10. 详解TCP的四报文挥手
  11. 电路杂谈——硬件经典面试题
  12. 51CTO学院三周年-51cto学院伴我成长
  13. 交换机软件测试,交换机性能测试方法
  14. Ubuntu 18.04配置静态IP地址
  15. 如何制作马赛克是硬质纤维板应该正确基金会对于马赛克
  16. 第五章-系统的频域分析
  17. java 压缩图片至指定大小
  18. QQ邮箱IMAP/SMTP服务,设置 未成功原因
  19. php下拉框css样式,纯CSS实现的下拉菜单
  20. Python jieba分词如何添加自定义词和去除不需要长尾词

热门文章

  1. iTerm2 使用笔记
  2. Java Error(三)
  3. java快速寻找一个数组的最大值或最小值, min, max,三种方法
  4. SVN报错working copy is not uptodate
  5. 重新实践《轻量级DJANGO》这本书
  6. Runtime.getRuntime().exec()
  7. node.js学习笔记14—微型社交网站
  8. SQL Server 2008 阻止保存要求重新创建表的更改
  9. Windows下多线程编程技术及其实现
  10. 用Unity3D实现简单的牧师与魔鬼游戏(动作分离版)