HashMap 内部原理
HashMap 内部实现
通过名字便可知道的是,HashMap 的原理就是散列。HashMap内部维护一个 Buckets 数组。每一个 Bucket 封装为一个 Entry<K, V> 键值对形式的链表结构。这个 Buckets 数组也称为表。表的索引是 密钥K 的散列值(散列码)。
例如以下图所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveXloMzUyMDkxNjI2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">
链表的每一个节点是一个名为 Entry<K,V> 的类的实例。 Entry 类实现了 Map.Entry 接口,以下是Entry类的代码:
private static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    final int hash;
    V value;
    Entry<K,V> next;
}
注: 每一个 Entry 对象仅与一个特定 key 相关联。但其 value 是能够改变的(假设同样的 key 之后被又一次插入不同的 value) - 因此键是终于的,而值不是。 每一个Entry对象都有一个名为 next 的字段,它指向下一个Entry,所以实际上为单链表结构。hash 字段存储了 Entry 对象在 Buckets 数组索引,也就是 key 的散列值。
假设发生Hash碰撞,也就是两个key的hash值同样,或者假设这个元素所在的位子上已经存放有其它元素了,那么在同一个位子上的元素将以链表的形式存放,新增加的放在链头,最早增加的放在链尾。
影响 HashMap 性能的两个因素是初始容量和负载因子。容量是表数组的长度,初始容量仅仅是创建哈希表时的容量。负载因子是衡量哈希表在容量自己主动增加之前是否同意获取的量度(比例)。
当散列表中的 Entry 数量超过负载因子和当前容量的乘积时,将会又一次散列该表(也就是重建内部数据结构)。使得散列表具有大约两倍的容量(这个事实上和ArrayList相似)。
理解 put() 方法
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key); // 计算hash值
        int i = indexFor(hash, table.length); // 计算在数组中的索引
        // 遍历链表
        for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            // hash值同样而且key相等,就直接替换
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(hash, key, value, i); // 否则就增加到链表
        return null;
    }
注:这个计算出来的hash值被传递给内部哈希函数,哈希函数将返回密钥的散列值。这个值就是 bucket/数组 的索i引。
    static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }
这里就有个疑问了,我们怎样计算相应存储数组索引,首先想到的就是把hashcode对数组长度取模运算。也就是h%length,这样一来,元素的分布相对来说是比較均匀的。可是,“模”运算的消耗还是比較大的。能不能找一种更高速,消耗更小的方式那中?
首先算得key得hashcode值,然后跟数组的长度-1做一次“与”运算(&)。
看上去非常easy。事实上比較有玄机。比方数组的长度是2的4次方,那么hashcode就会和2的4次方-1做“与”运算。非常多人都有这个疑问,为什么hashmap的数组初始化大小都是2的次方大小时,hashmap的效率最高。我以2的4次方举例,来解释一下为什么数组大小为2的幂时hashmap訪问的性能最高。
例如以下图。左边两组是数组长度为16(2的4次方)。右边两组是数组长度为15。两组的hashcode均为8和9。可是非常明显,当它们和1110“与”的时候,产生了同样的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到同一个链表上,那么查询的时候就须要遍历这个链表,得到8或者9,这样就减少了查询的效率。
同一时候。我们也能够发现,当数组长度为15的时候,hashcode的值会与14(1110)进行“与”,那么最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这样的情况中,数组能够使用的位置比数组长度小了非常多。这意味着进一步增加了碰撞的几率。减慢了查询的效率!
上图參考自:http://blog.csdn.net/oqqYeYi/article/details/39831029
理解 get() 方法
    public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
    }
    final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }
        int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key); // 计算hash值
        // 依据索引遍历链表,找出相等的key
        for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }
get 与 put 总结
以下总结了 put() 和 get() 发生的三个重要步骤:
- 通过调用 计算 Hash Code 方法计算密钥的哈希码。
 - 将计算的散列码传递到内部散列函数
indexFor()以获取表的索引。 - 迭代通过在索引处出现的链表,并调用 
equals()方法来查找匹配键。 
所以在这之前要先理解 equals() 和 hashCode() 这两个方法。
在 Java8 中的改善
在Java 8中,对HashMap有一个性能上的改进。当密钥中存在很多哈希冲突(不同的密钥终于具有同样的哈希值或索引)时。平衡树将用于存储 Entry 对象,而不是链表。做法是。一旦 bucket 中的 Entry 数量增长超过某一阈值。则 bucket 将从 Entry 链表切换到平衡树。
HashMap 内部原理的更多相关文章
- HashMap实现原理
		
学习笔记之HashMap篇,简单学习了解HashMap的实现原理和扩容. 大家都知道HashMap处理数据很快,时间复杂度O(1),那么是怎么做到的呢?那就先了解一下常见数据结构. 一般来说,我们把存 ...
 - 理解HashMap的原理
		
HashMap内部数据结构 HashMap内部采用数组和链表结合的方式来存取数据(见下图).这种方式有什么好处呢? 我们知道,数组操作对于检索是O(1)的,能够很快的根据数组的下标定位对 ...
 - HashMap实现原理及源码分析之JDK8
		
继续上回HashMap的学习 HashMap实现原理及源码分析之JDK7 转载 Java8源码-HashMap 基于JDK8的HashMap源码解析 [jdk1.8]HashMap源码分析 一.H ...
 - java基础之hashcode理解及hashmap实现原理及MD5
		
HashCode值 1. hashcode值是int的,64位.int hashCode(). 2. java object类默认的hashcode()计算方法是根据对象的内存地址来计算的.所以可由此 ...
 - 深入分析 JDK8 中 HashMap 的原理、实现和优化
		
HashMap 可以说是使用频率最高的处理键值映射的数据结构,它不保证插入顺序,允许插入 null 的键和值.本文采用 JDK8 中的源码,深入分析 HashMap 的原理.实现和优化.首发于微信公众 ...
 - Java HashMap实现原理 源码剖析
		
HashMap是基于哈希表的Map接口实现,提供了所有可选的映射操作,并允许使用null值和null建,不同步且不保证映射顺序.下面记录一下研究HashMap实现原理. HashMap内部存储 在Ha ...
 - hashMap的原理
		
hashMap的原理分析(转载) 1.总结: HashMap是基于哈希表实现的,用Entry[]来存储数据,而Entry中封装了key.value.hash以及Entry类型的next HashMap ...
 - HashMap实现原理一步一步分析(1-put方法源码整体过程)
		
各位同学大家好, 今天给大家分享一下HashMap内部的实现原理, 这一块也是在面试过程当中基础部分被问得比较多的一部分. 想要搞清楚HashMap内部的实现原理,我们需要先对一些基本的概念有一些了解 ...
 - [翻译]Java HashMap工作原理
		
大部分Java开发者都在使用Map,特别是HashMap.HashMap是一种简单但强大的方式去存储和获取数据.但有多少开发者知道HashMap内部如何工作呢?几天前,我阅读了java.util.Ha ...
 
随机推荐
- CountDownLatch用法---等待多个线程执行完才执行
			
CountDownLatch用法---等待多个线程执行完才执行 CountDownLatch用法---等待多个线程执行完才执行 CountDownLatch用法---等待多个线程执行完才执行 Coun ...
 - FORM-加载前指定日期时间格式
			
PRE-FORM -- Standard date format --BEGIN set_application_property(DATE_FORMAT_COMPATIBILITY_MODE, ' ...
 - 【web 回车】web项目 注册或登录页面 回车登录无效,解决方案
			
解决方案: /** * 登陆按钮的点击事件 */ $("#loginID").click(function(){ var username = $("#u"). ...
 - 【shiro】报错: If the controller requires proxying (e.g. due to @Transactional), please use class-based proxying.
			
spring整合shiro,项目报如下错误: ==============异常开始============= java.lang.IllegalStateException: The mapped c ...
 - 折腾mysql的小坑记录
			
1.安装 CentOS下先卸载自带的mariadb rpm -qa | grep mariadb mariadb-libs--.el7_2.x86_64 mariadb--.el7_2.x86_64 ...
 - Java集合类理解
			
深入Java集合学习系列:http://zhangshixi.iteye.com/blog/674856 http://blog.csdn.net/shf4715/article/details/47 ...
 - [转]sql server transaction
			
本文转自: http://www.2cto.com/database/201208/146734.html sql事务(Transaction)用法介绍及回滚实例 事务(Transaction)是 ...
 - iOS:第三方框架MJPhotoBrowser图片浏览器的使用
			
介绍:MJPhotoBrowser这个第三方库是MJ老师封装的一套用来浏览图片的浏览器,可是是本地图片.网络图片.gif图片等,其也依赖了SDWebImage.SVProgressHUD.YLGIFI ...
 - Oracle判断两个时间段是否相交
			
SQL中常常要判断两个时间段是否相交,该如何判断呢?比如两个时间段(S1,E1)和(S2,E2).我最先想到的是下面的方法一.方法一:(S1 BETWEEN S2 AND E2) OR (S2 BET ...
 - C# format 日期 各种 符号 实例分析如何精确C#日期格式到毫秒
			
摘 自: http://developer.51cto.com/art/200908/141145.htm 实例分析如何精确C#日期格式到毫秒 2009-08-03 10:48 paulfzm jav ...