您的位置:首页 > 编程语言 > Java开发

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

2018-03-30 16:17 686 查看
 一、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、存储结构图:
     


二、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);
    }

   //通过默认容量和默认加载因子构造Has
d8b9
hMap 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;
    }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: