一、写随笔的原因:HashMap我们在平时都会用,一般面试题也都会问,借此篇文章分析下HashMap(基于JDK1.8)的源码。

二、具体的内容:

1.简介:

  HashMap在基于数组+链表来实现的,能在查询和修改方便继承了数组的线性查找和链表的寻址修改。

2.工作原理:

  HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,计算并返回的hashCode是用于找到Map数组的bucket位置来储存Node 对象。这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Node 。如下图:

3.对照源码分析:

首先HashMap类里面有个Node<K,V>静态内部类,里面包含四个属性:hash,calue,value,next代码如下(主要看有注释的那四行,其他可以忽略):

    static class Node<K,V> implements Map.Entry<K,V> {
final int hash; // 根据key值计算出的hash值
final K key; // 写入的key值
V value; // 写入的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;
}
}

接下来看一下put方法的源码:

    public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

这里的hash(key)是计算出key所对应的hash值,继续看putVal()方法:

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; // 这里的tab就是table,后三行会赋值,下面我会直接说table而不是tab
     Node<K,V> p; // p就是table[i],后面也会赋值的
   int n, i;
if ((tab = table) == null || (n = tab.length) == 0) // table是否为空或者长度为0
n = (tab = resize()).length; //满足则调用resize()方法扩容
if ((p = tab[i = (n - 1) & hash]) == null) // 计算出索引i,如果table[i] == null
tab[i] = newNode(hash, key, value, null); // 直接插入
else { // 如果table[i] !=null
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //判断key是否存在了
e = p; //满足则直接覆盖旧值
else if (p instanceof TreeNode) // key不存在,继续判断是否table[i]是否是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) //如果链表长度大于等于8
treeifyBin(tab, hash); // 链表转化为红黑树
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) // 如果链表中存在相同的key,直接覆盖旧值
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold) // 容量达到阀值
resize(); //扩容
afterNodeInsertion(evict); // 这个方法HashMap里面是空的,LinkedHashMap有实现方法,意思就是为了实现顺序插入
return null;
}
具体分析看下面的图:

接下来看一下gēt方法的源码:

    public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value; //指定key 通过hash函数得到key的hash值
}

上面的内部getNode()方法是根据hash值,知道对应的Node,并返回。然后就可以获得到里面的value值了。

4.一些问题:

  1.HashMap中的碰撞探测(collision detection)以及碰撞的解决方法:

   当两个对象的hashcode相同时,它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用LinkedList存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在LinkedList中。这两个对象就算hashcode相同,但是它们可能并不相等。 那如何获取这两个对象的值呢?当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,遍历LinkedList直到找到值对象。找到bucket位置之后,会调用keys.equals()方法去找到LinkedList中正确的节点,最终找到要找的值对象使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。

  2.解决 hash 冲突的常见方法

     a. 链地址法:将哈希表的每个单元作为链表的头结点,所有哈希地址为 i 的元素构成一个同义词链表。即发生冲突时就把该关键字链在以该单元为头结点的链表的尾部。

     b. 开放定址法:即发生冲突时,去寻找下一个空的哈希地址。只要哈希表足够大,总能找到空的哈希地址。

   c. 再哈希法:即发生冲突时,由其他的函数再计算一次哈希值。

   d. 建立公共溢出区:将哈希表分为基本表和溢出表,发生冲突时,将冲突的元素放入溢出表。

   HashMap 就是使用链地址法来解决冲突的(jdk8中采用平衡树来替代链表存储冲突的元素,但hash() 方法原理相同)。数组中的每一个单元都会指向一个链表,如果发生冲突,就将 put 进来的 K- V 插入到链表的尾部。

三、总结:

这次hashmap大概就分析到这里,总的来说java8对HashMap进行了一些修改,最大不同使用了红黑树。当我们查找时,根据hash值来定位到数组的具体下标,之后再顺着链表一个一个的查找,时间复杂度为O(n),当链表中的元素达到8以后会将链表转化为红黑树,时间复杂度则降为O(logN),提高了效率。

部分参考:https://blog.csdn.net/visant/article/details/80045154。

HashMap原理探究的更多相关文章

  1. [原] KVM 虚拟化原理探究(1)— overview

    KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...

  2. [原] KVM 虚拟化原理探究 —— 目录

    KVM 虚拟化原理探究 -- 目录 标签(空格分隔): KVM KVM 虚拟化原理探究(1)- overview KVM 虚拟化原理探究(2)- QEMU启动过程 KVM 虚拟化原理探究(3)- CP ...

  3. [原] KVM 虚拟化原理探究(6)— 块设备IO虚拟化

    KVM 虚拟化原理探究(6)- 块设备IO虚拟化 标签(空格分隔): KVM [toc] 块设备IO虚拟化简介 上一篇文章讲到了网络IO虚拟化,作为另外一个重要的虚拟化资源,块设备IO的虚拟化也是同样 ...

  4. [原] KVM 虚拟化原理探究(5)— 网络IO虚拟化

    KVM 虚拟化原理探究(5)- 网络IO虚拟化 标签(空格分隔): KVM IO 虚拟化简介 前面的文章介绍了KVM的启动过程,CPU虚拟化,内存虚拟化原理.作为一个完整的风诺依曼计算机系统,必然有输 ...

  5. [原] KVM 虚拟化原理探究(4)— 内存虚拟化

    KVM 虚拟化原理探究(4)- 内存虚拟化 标签(空格分隔): KVM 内存虚拟化简介 前一章介绍了CPU虚拟化的内容,这一章介绍一下KVM的内存虚拟化原理.可以说内存是除了CPU外最重要的组件,Gu ...

  6. [原] KVM 虚拟化原理探究(3)— CPU 虚拟化

    KVM 虚拟化原理探究(3)- CPU 虚拟化 标签(空格分隔): KVM [TOC] CPU 虚拟化简介 上一篇文章笼统的介绍了一个虚拟机的诞生过程,从demo中也可以看到,运行一个虚拟机再也不需要 ...

  7. [原] KVM 虚拟化原理探究(2)— QEMU启动过程

    KVM 虚拟化原理探究- QEMU启动过程 标签(空格分隔): KVM [TOC] 虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_ ...

  8. 弱类型变量原理探究(转载 http://www.csdn.net/article/2014-09-15/2821685-exploring-of-the-php)

    N首页> 云计算 [问底]王帅:深入PHP内核(一)——弱类型变量原理探究 发表于2014-09-19 09:00| 13055次阅读| 来源CSDN| 36 条评论| 作者王帅 问底PHP王帅 ...

  9. js事件底层原理探究

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

随机推荐

  1. 【React自制全家桶】九、Redux入手

    一.React项目中为什么要用Redux 上图: 左图当使用纯React开发稍微大点的项目,因为React数据是瀑布式的,只能通过父子组件传递数据,所以实现关系不大的两React的组件之间的数据传递就 ...

  2. pandas基础,Serires,Dataframe

    DataFrame DataFrame是Pandas中的一个表格型的数据结构,包含有一组有序的列,每列可以是不同的值类型(数值.字符串.布尔型等),DataFrame即有行索引也有列索引,可以被看做是 ...

  3. centos6密钥验证

    密钥验证: 公钥(服务器上)私钥(客户端)在远程登录软件上可生成SSH密钥对.在服务器上建目录.SSH 再在其中建文件authorized_keys,复制公钥到服务器上此文件中. (1)selinux ...

  4. CKeditor从Word粘贴格式问题

    在config.js中添加配置 config.pasteFromWordRemoveFontStyles = false;    config.pasteFromWordRemoveStyles = ...

  5. Xcode8.1 真机测试 ,添加iOS10.3的idk到Xcode8.1中

    1.下载iOS10.3的idk包; 2.解压, 找到路径 Finder -> 应用程序 -> 右键Xcode -> 显示包内容 -> Contents -> Develo ...

  6. 【.NET】ASP.Net IE10+ SCRIPT:XXX_doPostBack 未定义

    问题描述 GridView中分页控件,点击分页无反应,Linkbutton点击无反应,打开Web控制台,发现如下错误:SCRIPTXXX:_doPostBack 未定义:查询后得知,是由于.NET F ...

  7. webdriervAPI(多窗口切换)

    两个方法 driver.current_window_handle 获取当前窗口句柄 driver.window_handles 获取所有窗口句柄 from selenium import webdr ...

  8. python 实现 灰色预测 GM(1,1)模型 灰色系统 预测 灰色预测公式推导

    来源公式推导连接 https://blog.csdn.net/qq_36387683/article/details/88554434 关键词:灰色预测 python 实现 灰色预测 GM(1,1)模 ...

  9. 【Linux开发】彻底释放Linux线程的资源

    Linux系统中程序的线程资源是有限的,表现为对于一个程序其能同时运行的线程数是有限的.而默认的条件下,一个线程结束后,其对应的资源不会被释放,于是,如果在一个程序中,反复建立线程,而线程又默认的退出 ...

  10. Flume 概念、模型和特点

    Flume Event - Flume 事件 - 被定义为一个具有有效荷载的字节数据流和可选的字符串属性集. Flume Agent- Flume - 代理 - 是一个进程承载从外部源事件流到下一个目 ...