JDK1.7HashMap源码分析

前言:

HashMap是我们在编程过程中非常常见的一个数据结构

主要功能是存放键值对(K-V)

我们都知道HashMap在jdk7中是以数组+链表的形式构成的(JDK8的改动还是比较大的,数组+链表/红黑树构成)

HashMap中数组和链表的关系如下

 

类似这个形状:

 

 那么HashMap源码的原理到底是怎么样的呢,我们来分析一下

一、图解流程

 二、源码分析

分析一:put方法分析

1.我们new一个HashMap对象

2.点进方法

这里有两个成员变量,通过看注释我们知道

DEFAULT_INITIAL_CAPACITY是默认初始容量-必须是2的幂。

DEFAULT_LOAD_FACTOR是构造函数中未指定时使用的负载因子。

3.我们使用put存入一个键值对

点进put方法查看

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
        //若table空 就是哈希表没有初始化
        //使用构造函数时设置的阈值(初始容量)初始化数组table
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);//如果key是null,就没办法计算hash值,所以放到table的第一个位置
        int hash = hash(key);//根据key计算hash值的方法
        int i = indexFor(hash, table.length);//根据hash值 获得key应该存放的数组table中位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {//遍历这个位置的元素内容
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                //如果这个key已经存在了,就直接覆盖掉
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                //返回被覆盖掉的value
                return oldValue;
            }
        }
        modCount++;
        //该key不存在,放入链表中(jdk7是采用的头插的方法)        
        addEntry(hash, key, value, i);
        return null;
    }

 put方法流程图如下:

 4.看初始化数组方法

private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);//这个方法是将toSize值转变成2的幂,如果toSize=6,则capacity=8,2的4次方

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);//重新计算阈值 threshold = 容量 * 加载因子
        table = new Entry[capacity];//用新计算的容量初始化数组table 
        initHashSeedAsNeeded(capacity);
    }

分析二:hash计算分析

final int hash(Object k) {
//将 键key 转换成 哈希码(hash值)操作 = 使用hashCode() + 4次位运算 + 5次异或运算(9次扰动)
        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);
    }

但是这个hash值并不是数组的下标还要有一个运算才能得出下标:

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);//将hash值与数组长度-1作与运算就是数组的下标
    }

以上的种种干扰变化可以有效的减少碰撞

分析三:transfer扩容机制分析:

我们在put方法的时候发现一个给数组赋值的方法addEntry

void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {//发现数组长度不够了
            resize(2 * table.length);//调用扩容函数,新数组的长度要求是2倍的旧数组长度
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

扩容函数:

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;//旧数组长度
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];//创建新的数组
        transfer(newTable, initHashSeedAsNeeded(newCapacity));//转移旧数组内容到新的数组
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);//新的阈值
    }

转移数组函数:

void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {//遍历就数组的元素
            while(null != e) {//元素不为空时
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);//重新计算元素对应的新数组下标值
                e.next = newTable[i];
                newTable[i] = e;
                e = next;//链表下一个节点,防止链表丢失
            }
        }
    }

流程图解:

 分析四:获取元素源码分析

get方法

public V get(Object key) {
        if (key == null)//如果key是null直接返回数组第一个元素,因为数组第一个元素就是放的key=null
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);//获取value方法

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

进入getEntry方法查看

final Entry<K,V> getEntry(Object key) {
        if (size == 0) {//数组长度为0,没有元素也就是获取不到就返回null
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);//计算该key的hash值
        for (Entry<K,V> e = table[indexFor(hash, table.length)];//indexFor函数算出下标,根据下标获取键值对对象
             e != null;
             e = e.next) {//遍历该位置的链表直到找到对应的key,如果没有旧直接返回null就好了
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

有了put方法的基础,get方法不论实现还是理解都是比较好理解的

获取元素流程图解:

 

到这里我们分析完了HashMap的put和get最主要的两个方法,这篇博客先写这么多,休息一下太累了

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值