`

Java Core系列之HashMap实现

    博客分类:
  • J2SE
 
阅读更多
转自:http://www.blogjava.net/DLevin/archive/2013/10/15/404984.html
 

最近在看Guava中的Cache的源码,它的实现基于ConcurrentHashMap,前段时间组里招人,据说很多看起来很牛掰的简历,一个 HashMap就能刷掉很多,所以顺便把HashMap和ConcurrentHashMap的源码复习一遍。先从HashMap开始 (另:Hashtable是HashMap的线程安全版本,它的实现和HashMap实现基本一致,除了它不能包含null值的key和value,并且 它在计算hash值和数组索引值的方式要稍微简单一些。对于线程安全的实现,Hashtable简单的将所有操作都标记成synchronized,即对 当前实例的锁,这样容易引起一些性能问题,所以目前一般使用性能更好的ConcurrentHashMap)。

Map是对键值对存储的抽象,因而其最主要的方法有:
1. 添加新的键值对(key,value);
2. 通过键(key)查找关联的值(value);
3. 通过键(key)移除关联的值(value);
4. 判断键(key)或值(value)的存在性。
其他的方法有:判断键值对的空属性以及目前的键值对数,获取所有键、所有值或者所有键值对的集合,批量添加,清除所有键值对等。在Map中,一个键值对用Entry接口来表示。因而在Java中,对Map接口的定义如下:

public interface Map<K,V> {
    boolean isEmpty();
    boolean containsKey(Object key);
    boolean containsValue(Object value);
    V get(Object key);
    V put(K key, V value);
    V remove(Object key);
    void putAll(Map<? extends K, ? extends V> m);
    void clear();
    Set<K> keySet();
    Collection<V> values();
    Set<Map.Entry<K, V>> entrySet();

    interface Entry<K,V> {
        K getKey();
        V getValue();
        V setValue(V value);
        boolean equals(Object o);
        int hashCode();
    }

    boolean equals(Object o);
    int hashCode();
}

HashMap是哈希表对Map非线程安全版本的实现,它允许key为null,也允许value为null。所谓哈 希表就是通过一个哈希函数计算出一个key的哈希值,然后使用该哈希值定位对应的value所在的位置;如果出现哈希值冲突(多个key产生相同的哈希 值),则采用一定的冲突处理方法定位到正真value位置,然后返回查找到的value值。一般哈希表内部使用一个数组实现,使用哈希函数计算出key对 应数组中的位置,然后使用处理冲突法找到真正的value,并返回。因而实现哈希表最主要的问题在于选择哈希函数和冲突处理方法,好的哈希函数能使数据分 布更加零散,从而减少冲突的可能性,而好的冲突处理方法能使冲突处理更快,尽量让数据分布更加零散,从而不会影响将来的冲突处理方法。

在严蔚敏、吴伟明版本的《数据结构(C语言版)》中提供的哈希函数有:1. 直接定址法(线性函数法);2. 数字分析法;3. 平方取中法;4. 折叠法;5. 除留余数法;6. 随机数法。在JDK的HashMap中采用了移位异或法后除留余数(和2的n次方'&'操作)。HashMap内部的数据结构是一个 Entry<K, V>的数组,在使用key查找value时,先使用key实例计算hash值,然后对计算出的hash值做各种移位和异或操作,然后取其数组的最大 索引值的余数('&'操作,一般其容量值都是2的倍数,因而可以认为是除留余数)。在JDK 1.7中对String类型采用了内部hash算法(当数组容量超过一定的阀值,使用“jdk.map.althashing.threshold”设置 该阀值,默认为Integer.MAX_VALUE,即关闭该功能),并且使用了一个hashSeed作为初始值,不了解这些算法的具体缘由,就这样浅尝 辄止了。

    final int hash(Object k) {
        int h = 0;
        if (useAltHashing) {
            if (k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
            h = hashSeed;
        }
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

 

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

同样在上述的数据结构书中关于冲突处理提供了几个方法:1. 开放定址法;2. 再哈希法;3.链地址法;4. 建立一个公共溢出区法。在JDK的HashMap中采用了链地址法,即每个数组bucket中存放的是一个Entry链,每次新添加一个键值对,就是向链 头添加一个Entry实例,新添加的Entry的下一个元素是原有的链头(如果该数组bucket不存在Entry链,则原有链头值为null,不影响逻 辑)。每个Entry包含key、value、hash值和指向下一个Entry的next指针。

    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
    }


添加
从以上描述中,我们可以知道添加新的键值对可以分成两部分:
1. 使用key计算出内部数组的索引值(index)。
2. 如果该索引的数组bucket中已经存在Entry链,并且该链中已经存在新添加的key的值,则将原有的值设置成新添加的值,并返回旧值。
3. 否则,创建新的Entry实例,将该实例插入到原有链的头部。
4. 在新添加Entry实例时,如果当前size超过阀值(capacity * loadFactor),数组容量将会自动扩大两倍,在数组扩容时,所有原存在的Entry会重新计算索引值,并且Entry链的顺序也会发生颠倒(如果 还在同一个链中的话);而该新添加的Entry的索引值也会重新计算。
5. 对key为null时,默认数组的索引值为0,其他逻辑不变。

    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        createEntry(hash, key, value, bucketIndex);
    }
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

插入原理图:

查找
查找和添加类似,首先根据key计算出数组的索引值(如果key为null,则索引值为0),然后顺序查找该索引值对应的Entry链,如果在Entry 链中找到相等的key,则表示找到相应的Entry记录,否则,表示没找到,返回null。对get()操作返回Entry中的Value值,对于 containsKey()操作,则判断是否存在记录,两个方法都调用getEntry()方法:

    final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key);
        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 != null && key.equals(k))))
                return e;
        }
        return null;
    }

而对于value查找(如containsValue()操作)则需要整个表遍历(数组遍历和数组中的Entry链遍历),因而这种查找的效率比较低,代码实现也比较简单。

移除
移除操作(remove())也是先通过key值计算数组中的索引号(当key为null时索引号为0),从而找到Entry链,查找Entry链中的Entry,并将该Entry删除。

遍历
HashMap 中实现了一个HashIterator,它首先遍历数组,查找到一个非null的Entry实例,记录该Entry所在数组索引,然后在下一个 next()操作中,继续查找下一个非null的Entry,并将之前查找到的非null Entry返回。为实现遍历时不能修改HashMap的内容(可以更新已存在的记录的值,但是不可以添加、删除原有记录),HashMap记录一个 modCount字段,在每次添加或删除操作起效时,将modCount自增,而在创建HashIterator时记录当前的modCount值 (expectedModCount),如果在遍历过程中(next()、remove())操作时,HashMap中的modCount和已存储的 expectedModCount不一样,表示HashMap已经被修改,抛出ConcurrentModificationException。即所谓 的fail fast原则。
在HashMap中返回的key、value、Entry集合都是基于该Iterator实现,实现比较简单,不细讲。

注:clear()操作引起的内存问题-由于clear()操作只是将数组中的所有项置为null,数组本身大小并不改变,因而当某个HashMap已存储过较大的数据时,调用clear()有些时候不是一个好的做法。

最后吐槽一下,即使JDK内部的HashMap也有很多的代码重复。。。。。

分享到:
评论

相关推荐

    java8源码-JavaRobot:Java学习笔记,JavaLearningNote

    java8 源码 学习笔记(持续更新中) 所有文章均同步发布到微信公众号【JavaRobot】,关注微信公众号,及时得到文章推送,谢谢支持。 说明:如无特别说明,所有代码都...JavaConcurrent(Java并发系列) 【Java并发系列】

    spring4.0 API

    java.util.HashMap,V&gt; (implements java.lang.Cloneable, java.util.Map,V&gt;, java.io.Serializable) java.util.LinkedHashMap,V&gt; (implements java.util.Map,V&gt;) org.springframework.core.annotation....

    达内 coreJava 习题答案

    import java.util.Scanner; class Bissextile{ public static void main(String[] arge){ System.out.print("请输入年份"); int year; //定义输入的年份名字为“year” Scanner scanner = new Scanner(System.in...

    java7hashmap源码-UserActionAnalyzePlatform:用户操作分析平台

    java7 hashmap源码 电商用户行为分析大数据平台 项目介绍 1.基于Spark开发的平台 2.需要有spark基础 3.有很多高级知识和设计模式 4.电商用户行为分析大数据平台(项目名称) 5.访问行为,购物行为,广告点击行为,对...

    java7hashmap源码-lf-mkd-java-core-high-level:来自慕课大学的java核心技术(高阶),主要内容:语法糖

    hashmap源码 CoreHighLevel 课程来源: 高级特性: 换算 字节 (byte):8个二进制位为一个字节(B),最常用的单位。计算机存储单位一般用B,KB,MB,GB,TB,PB,EB,ZB,YB,BB来表示,它们之间的关系是:  1B...

    java面试宝典

    coreJava部分 8 1、面向对象的特征有哪些方面? 8 2、作用域public,private,protected,以及不写时的区别? 8 3、String 是最基本的数据类型吗? 8 4、float 型float f=3.4是否正确? 8 5、语句float f=1.3;编译能否...

    java7hashmap源码-UserActionAnalyzePlatform-learn:电商用户行为分析大数据平台-spark

    java7 hashmap源码 电商用户行为分析大数据平台 项目介绍 1.基于Spark开发的平台 2.需要有spark基础 3.有很多高级知识和设计模式 4.电商用户行为分析大数据平台(项目名称) 5.访问行为,购物行为,广告点击行为,对...

    spark:在sparkjava中设置基本项目,显示如何在spark java中将代码组织为MVC

    依赖关系Sparkjava一个微型Web框架com.sparkjava spark-core 2.3 在Main.java中,我们拥有所有路线所有控制器都在controller文件夹中,该文件夹扩展了与每个Controller类相对应的基础Controller类,那里是template...

    微信开发java封装好的代码(密文解析,xml转化,验证,获取返回消息

    AccessTokenUtil,专门用于获取Accesstoken JacksonUtils,把解析好的XML文件转化为HashMap MessageUtil,获得要回复的消息,解析...SignUtil微信验证工具 CoreServlet具体的调用方法 CoreService.java具体的调用方法

    java_core_education_tasks

    该项目包含Java QA自动化工程师任务的几个样本。 任务和链接列表如下: 任务1: 编写简单的控制台计算器(至少实现4个操作:加法,减法,乘法,除法); b。 在列表或数组中按长度查找第二个String。 涵盖所有单元...

    千方百计笔试题大全

    coreJava部分 8 1、面向对象的特征有哪些方面? 8 2、作用域public,private,protected,以及不写时的区别? 8 3、String 是最基本的数据类型吗? 8 4、float 型float f=3.4是否正确? 8 5、语句float f=1.3;编译能否...

    Android_Packer:动态加载框架

    由于在较低API的版本中,/frameworks/base/core/java/android/app/ActivityThread.java中使用了HashMap,而高API版本中相对应的是ArrayMap,由此可造成不兼容。(final ArrayMap&lt;String&gt; mPackages = new ArrayMap()...

    常见的Java笔试题-JVM-JUC-Core:JUCJVM核心知识点

    常见的Java笔试题 JUC、JMM核心知识点笔记 尚硅谷周阳老师课程——笔记。 / / JUC知识点 JMM volatile关键字 可见性 原子性 有序性 哪些地方用到过volatile? 单例模式的安全问题 CAS CAS底层原理 CAS缺点 ABA问题 ...

    JavaITCourses-master

    该存储库将包含所有代码,并带有针对Java Core的少量注释。 有关更多信息,请参见和。 变更日志 带有实验室代码并针对以下每个课程进行测试: Java基础 术语 控制构造 数字,位运算 数组,字符串 基本算法 迭代...

    halbuilder-core:HalBuilder核心

    Halbuilder是一个简单的Java API,用于生成和使用符合HAL文档。 产生本地资源 Map&lt; String&gt; friend = HashMap . of( " name " , " Mike " , " age " , 36 ); ResourceRepresentation&lt; Map&gt; &gt; owner = ...

    Spring Cloud Finchley SR2全套(集成Spring Gateway)

    ├──cloud-service-core───────────────基础核心模块 ├──cloud-service-tools──────────────全局通用工具类 ├──cloud-service-reids──────────────Redis二次...

    cms后台管理

    Map, TemplateModel&gt; paramWrap = new HashMap, TemplateModel&gt;( params); //OUT_LIST值为tag_list,在类DirectiveUtils中声明,将内容列表放入其中 paramWrap.put(OUT_LIST, DEFAULT_WRAPPER.wrap(list)); //将...

Global site tag (gtag.js) - Google Analytics