HashMap实现分析

  HashMap最基本的实现思想如下图所示,使用数组加链表的组合形式来完成数据的存储。

   

  Entry在数组中的位置是由key的hashcode决定的。

  向一个数组长度为16,负载因子为0.75的HashMap中插入key的hashcode为26、126、1、337、184、12、31、111的对象后的结构为:

  

  1%16 =1 ,337%16 =1。数组中存储的是最后插入的数据,并用next指针指向之前已经存在的数据。

  

  HashMap查找数据的依据是:现根据key的hashcode查找位于数组中的位置,在使用next依次遍历链表中的元素,调用key的equals方法,如果key equals Entry对应的key,则Entry中的value就是所找的值。所以使用对象作为HashMap的key时,重写hashcode方法的同时需要重写equals方法。

  可以参考HashMap的get方法的代码,就会更清楚上面的描述:

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }

  HashMap中的Hash算法是经过了优化之后的,可以看到

  int hash = hash(key.hashCode());对hashcode又进行了二次散列操作,这样做的目的是使得计算出的hash比hashcode在数组上的分布将更为均匀,HashMap的空间利用率也越高。

  HashMap对hashcode的二次散列如下: 

static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

  HashMap在使用hash计算位于数组中的位置时也不是简单的%操作,而是用的indexFor来完成的。%操作比较耗资源,当HashMap中数组的length是2 的n次方时,h& (length-1)运算等价于h%length,但是&比%具有更高的效率。

static int indexFor(int h, int length) {
        return h & (length-1);
    }

  理解了二次散列和indexFor,上面的代码就比较的好理解了。table数组就是图中的Entry数组。

  HashMap可以存储key为null的Entry,该Entry将被放在数组指标为0的位置。

  

  当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中。最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。 

  那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

  

  HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

  这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。

  在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map:

  modCount的修饰符为volatile,保证线程之间修改的可见性。

  HashSet的底层也是用HashMap来实现的,使用HashMap的key来进行存储与散列。

时间: 2024-10-11 18:26:47

HashMap实现分析的相关文章

HashMap深度分析

java hashmap深度分析(转)   java.util.HashMap是很常见的类,前段时公司系统由于对HashMap使用不当,导致cpu百分之百,在并发环境下使用HashMap 而没有做同步,可能会引起死循环,关于这一点,sun的官方网站上已有阐述,这并非是bug. HashMap的数据结构          HashMap主要是用数组来存储数据的,我们都知道它会对key进行哈希运算,哈系运算会有重复的哈希值,对于哈希值的冲突,HashMap采用链表来解决的.在HashMap里有这样的

Java基础系列之(三) - HashMap深度分析

这次主要是分析下HashMap的工作原理,为什么我会拿这个东西出来分析,原因很简单,以前我面试的时候,偶尔问起HashMap,99%的程序员都知道HashMap,基本都会用Hashmap,这其中不仅仅包括刚毕业的大学生,也包括已经工作5年,甚至是10年的程序员.HashMap涉及的知识远远不止put和get那么简单.本次的分析希望对于面试的人起码对于面试官的问题有所应付  一.先来回忆下我的面试过程  问:“你用过HashMap,你能跟我说说它吗?”  答:“当然用过,HashMap是一种<ke

HashMap的分析(转)

一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同.)此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap. Map map = Coll

HashMap死锁分析

一.HashMap底层实现 简单的可以从以下两个纬度去理解HashMap的底层实现原理. 数组:充当索引 链表:处理碰撞 HashMap用一个指针数组table,离散化key的作用,当加入一个 key 的时候,通过Hash算法,计算出 key所在的数组下标 i,如果table[i]位置的对象元素为null的时候,则直接将<key, value>加入即可:但是,如果table[i]位置已经被占用的话,则会发生冲突碰撞:此时,会在 table[i]上形成一个链表. 如果table太小,就会发生频繁

JDK源码之HashMap 类分析

一 概述 HashMap实现 hashmap继承了AbstractMap,实现了Map接口和Cloneable接口,HashMap是基于哈希表(散列表),实现Map接口的双列集合 jdk8中底层数据结构已经改为二叉树,之前是链表 看hashmap之前,需要把Map,AbstractMap源码撸一遍,这里放我的博文链接: https://www.cnblogs.com/houzheng/p/12687883.html 涉及到的数据结构 二 源码分析 属性 静态内部类(Entry的实现) 三 总结

JAVA中hashmap的分析

从http://blog.csdn.net/luanlouis/article/details/41576373?utm_source=tuicool&utm_medium=referral学习到了java中hashmap的内部原理,非常不错的文章,非常感谢作者的奉献.以下为主要内容摘录. java/scala/c# 语言之中,class 都会含有hashcode,equals之类的方法,这是为什么?数组和链表的区别是什么?在编程开发中应该如何使用?在HashMap内部,采用了数组+链表的形式来

HashMap 原理分析

HashMap中key的存放原理: 1.创建Set集合 HashSet 其容器数组默认大小为16 也就是将容器分为16个区域,每一个区域存放的是链表. 2.现有一个元素想存放到set集合中,第一步要确定要放在哪个区域里面.方法是先取元素的hashcode,将这个值与1111进行与运算 所得到的值一定是0~15之间的数,这个数字决定了该元素会存放在set集合中的哪个区域里面. 3.已经决定了存放在那个区域,就检查这个区域的使用情况,如果该区域没有任何元素,就将新元素直接放置在这个区域中. 4.如果

android SparseArray替代HashMap的分析

SparseArray是Android框架独有的类,在标准的JDK中不存在这个类.它要比 HashMap 节省内存,某些情况下比HashMap性能更好,按照官方问答的解释,主要是因为SparseArray不需要对key和value进行auto-boxing(将原始类型封装为对象类型,比如把int类型封装成Integer类型),结构比HashMap简单(SparseArray内部主要使用两个一维数组来保存数据,一个用来存key,一个用来存value)不需要额外的额外的数据结构(主要是针对HashM

HashMap 扩容分析

使用HashMap时我们需要注意一下几点问题: 1.HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现.与HashTable主要区别为不支持同步和允许null作为key和value.  2.HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致.  3.如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap.  4.在JDK1