java集合HashMap源码分析(JDK1.7)

 一、HashMap相关知识简单介绍

   在HashMap的底层采用的数据存储结构为哈希表,通过hash算法值去确定数据存放的位置,并且还会存在哈希冲突(也叫左hash碰撞等问题);那么在学习HashMap之前,我们需要对HashMap存储结构的数据结构做一个简单的的相关理论知识做个了解。

 1、什么式哈希表(hash table)?

       哈希表(hash table 也叫散列表)是一种根据关键码值(key value)进行数据访问的数据结构,通过关键码值映射(函数)到连续数据存储区的某个位置来进行快速定位查找、存储数据,这种映射定位函数称为hash函数(也叫散列函数),而通过这种形式来存储或查询数据的连续存储数据区就叫做哈希表(hash table)。

2、什么是hash函数?

  简单的说就是根据数字计算,返回在某个特定区域内值的关系映射函数   特定范围值=f(关键字码)

3、什么是hash冲突?

   hash冲突指在进行数据插入时候,出现在通过hash函数计算出来的数据存储位置已经存在数据的情况。那么两个数据存放在同一个位置是不可行的,这就是hash冲突(例如:放中药的时候本来打算放在某个格子内,可是这个格子已经放了其他中药把这个格子占了,我们就不能放在这个格子了,这两种只能存放一种,不能一起放)

4、解决hash冲突的方式主要有哪些?

   A:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址)

   B::再散列函数法(发生冲突,再次使用hash函数再次计算)

   C:链地址法(以链表的形式存储 是HashMap的底层实现(数组+单向链表))

5、链表的种类

   单向链表、单向循环链表、双端链表、双端循环链表

6、存储结构图:

     《java集合HashMap源码分析(JDK1.7)》


二、HashMap源码分析之静态内部类Entry

      1、  HashMap的内部存储结构是数组和单向链表的结合。当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组,这个长度被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。 每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。 Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。 Entry是HashMap中的一个静态内部类。代码如下:

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

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

    2、Entry<K,V>静态内部类重要方法

  //获取key值     

   public final K getKey() {

            return key;

        }

       //获取value值

        public final V getValue() {

            return value;

        }

    //设置value值

    public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;

        }

  //判断两个结点对象是否相等

 public final boolean equals(Object o) {

            //判断类型

            if (!(o instanceof Map.Entry))

                return false;

           //类型相同,将Object对象结点装换为Entry对象

             Map.Entry e = (Map.Entry)o;

           //获取自身对象key和比较对象key

            Object k1 = getKey();

            Object k2 = e.getKey();

             //equals为Object对象的判断方法   是通过== 比较两个对象的,比较的是地址值,如果对象重写了Object对象的                      //  Object类的equals方法,则按照重写的比较方式算

            if (k1 == k2 || (k1 != null && k1.equals(k2))) {

                Object v1 = getValue();

                Object v2 = e.getValue();

             //key相等 ,在以相同的方式比较value值

                if (v1 == v2 || (v1 != null && v1.equals(v2)))

                    return true;

            }

            return false;

        }

      //key的hashCode值^value值的hashCode值

        public final int hashCode() {

               //Object对象的hashCode函数  底层不知道怎么实现,没有提供源码

               return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());

           }

       //重写Object类对象的toString方法

       public final String toString() {
               return getKey() + “=” + getValue();

          }

三、HashMap源码分析

     1、重要属性

    //默认初始化化容量,即16

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

//最大容量,即2的30次方

static final int MAXIMUM_CAPACITY = 1 << 30;

//默认负载因子值

static final float DEFAULT_LOAD_FACTOR = 0.75f;

//HashMap内部的存储结构是一个数组,此处数组为空,即没有初始化之前的状态

static final Entry<?,?>[] EMPTY_TABLE = {};

//空的存储实体

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

//实际存储的key-value键值对的个数

transient int size;

//阈值,当table == {}时,该值为初始容量(初始容量默认为16);当table被填充了,也就是为table分配内存空间后,threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold

int threshold;

//负载因子,代表了table的填充度有多少,默认是0.75

final float loadFactor;

//用于快速失败,由于HashMap非线程安全,在对HashMap进行迭代时,如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作),需要抛出异常ConcurrentModificationException

transient int modCount;

//默认的threshold值

static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

//计算Hash值时的用到

transient int hashSeed = 0;

     2、构造方法

    //通过初始容量和加载因子构造HashMap

    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;

        threshold = initialCapacity;

        init();

    }


//通过容量构造HashMap,加载因子取默认值,即0.75f

    public HashMap(int initialCapacity) {

        this(initialCapacity, DEFAULT_LOAD_FACTOR);

    }

   
//通过默认容量和默认加载因子构造HashMap 16和0.75f

    public HashMap() {

        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);

    }

   //
通过其他Map来初始化HashMap,容量通过其他Map的size来计算,装载因子取0.75

    public HashMap(Map<? extends K, ? extends V> m) {

        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,

                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);

        inflateTable(threshold);

        putAllForCreate(m);

    }

3、V put(k key,V value)  添加元素

public V put(K key, V value) {

//如果table数组为空数组{},进行数组填充(为table分配实际内存空间)

//入参为threshold,此时threshold为initialCapacity 默认是1<<4(=16)

if (table == EMPTY_TABLE) {

inflateTable(threshold);//分配数组空间 16

}

//如果key为null,存储位置为table[0]或table[0]的冲突链上

if (key == null)

return putForNullKey(value);

//通过key值调用hash函数得到hashcode值

int hash = hash(key);

//对key的hashcode进一步计算,确保散列均匀

int i = indexFor(hash, table.length);

//获取在table中的实际位置

for (Entry<K,V> e = table[i]; e != null; e = e.next) {

//如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧value

Object k;

if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

V oldValue = e.value;

e.value = value;

e.recordAccess(this);//调用value的回调函数,其实这个函数也为空实现

return oldValue;

}

}

modCount++;//保证并发访问时,若HashMap内部结构发生变化,快速响应失败

addEntry(hash, key, value, i);

//新增一个entry

return null;

}

2、HashMap中的hash算法(注意jdk1.7和1.8有区别)

  //用了很多的异或,移位等运算, 这里我们可以不用深究原理

   final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

  3、确定存储位置index   

static int indexFor(int h, int length) {
        // key值的hash函数值和数组长度-1做与运算
        return h & (length-1);
    }

4、集合元素个数(Entry个数)

 public int size() {
        return size;
    }

5、获取集合元素数据

   public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }

6、当元素的key值为null时候获取方式

private V getForNullKey() {
        if (size == 0) {
            return null;

        }

         //因为hash算法实现null存在0的位置

        for (Entry<K,V> e = table[0]; e != null; e = e.next) {

            if (e.key == null)

                return e.value;

        }

        return null;

    }

    原文作者:java集合源码分析
    原文地址: https://blog.csdn.net/ao_dream/article/details/79756598
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞