HashMap原理机制详解

大家在求职应聘java开发岗时,想必会经常被面试官问到HashMap是怎么实现的问题,本文

通过jdk源码来简析HashMap的实现机制。

首先,HashMap继承了AbstractMap,并实现了Map、Cloneable和Serializable接口,这里不

作阐述。在Eclipse里查看源码,可得如下类结构截图。


首先来看成员变量,注意到有一个Entry类型的数组table,这个其实就是真正用来存储我们

的数据的地方,其中Entry是HashMap的一个静态内部类,它包含一个next引用(源代码如

下),其实它就是一个简单的链表结构

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

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

到这里,我们其实可以知道HashMap是怎么回事了,它底层是利用数组(还记得那个table么?)实现,数组每一个元素本身相当于一

个链表,我们的数据就存在这些个链表的域中!我们知道数组存取速度快,链表增删方便,HashMap是在存取和增删这两方面取了一

个折中!结构图如下


是不是很简单?

我们再看前三个final变量,根据名字就知道,分别是HashMap的默认初始容量、最大容量

、默认负载因子。不错,HashMap也是有最大容量的(2的30次方),当然平时我们都没

机会让它爆掉。为了节省空间,HashMap一开始只是申请一个相对较小的空间(一般就是

默认的容量数值这里是2的16次方)。对于负载因子,这里有必要提一下,它和HashMap

的容量有关,比如负载因子为0.75,表示12单位的空间只存储12*0.75=8个单位,这个数

字8其实也就是threshold的意义!而size变量表示真个map存储的键值对的个数,在后文

对put函数讲解得时候会说到HashMap的扩容方式,它的判断标准也是根据这个将size和

threshold变量进行大小比较。

再看它的构造函数,其中有四个构造函数,我们平时用的最多是第三个无参构造函数,

其源码如下:

 public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }

这里做一些基本的初始化工作,注意threshold等于什么!这在之前已经说过。另外,

init()是个空方法,这里不太清楚原因,有知道的网友,多多指教啊!其它三个构

造函数其实最终都是调用调用第一个构造函数,只是使用传入的参数进行初始化。

接下来,看看两个最具代表性的方法:put、get

对于添加键值对对象的put方法:

   public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        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))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

该方法,首先判断key是否为null,如果null则调用putForNullKey方法,查看源码发现putForNullKey方法默认

将Null对应的value放在table[0]位置所在的entry链表上(因为有的key会映射到该

位置),所以HashMap是支持null为key的!然后,通过就是计算key对应的hash

值,hash和indexFor函数如下:

 static int hash(int h) {
        // 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);
    }

    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

没啥特别要说的,大底知道是通过无符号位移操作再与table的长度进行与操作,且不提。在put函数的最后有一行

 addEntry(hash, key, value, i);

也就是添加元素到对应的链表中,其源码为:

 void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }
看到其中的if 语句了么? 当元素越来越多时,hashmap的扩容方式为扩大一倍!

再接着,通过k的hashcode和k值判断是否冲突!如果冲突则将

value覆盖,并返回覆盖之前的value,当然,没有冲突和发生冲突的key值为

null时都返回null (0.0, 别晕)

接下来,看看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;
    }

同put一样,先判断key值是否为null。 然后通过计算hashcode形成对table的一个映射,找到所在的entry链表,

然后就是对该链表进行简单的遍历,蛮简单,不多提了!

最后提一下,table数组是声明为transient的,这可以防止HashMap被序列化。

另外,HashMap还有很多内部iterator接口的实现类,其主要是对HashMap

提供不需要了解内部结构就可以进行访问的目的,这个可以参考iterator设

计模式 。










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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值