JAVA之HashMap实现原理分析-1

1、HashMap一般使用场景:

HashMap为key-value对存储方式,允许key或者value的值为null,这也是与HashTable的区别之一,在本人所做的项目中,一般使用HashMap来做系统内部数据缓存,例如:需要通过一个设备的唯一标识来快速找到到设备的实体对象,或者需要快速定位到某一对象进行操作,如果您也有这样的需求,在合适的场景下可以考虑使用HashMap来处理。当然这不只是HashMap的唯一用处。

2、HashMap使用疑问:

HashMap是如何快速根据key值定位到value值的?在使用HashMap的时候,如果多次调用put方法的key值重复,HashMap的底层又是如何处理的?下面逐一分析。

3、HashMap结构:

根据源码分析,HashMap内部其实是由Entry[] table构成,在新建一个HashMap的时候JDK代码如下:

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

由此可见,HashMap的内部其实维持了一个Entry数组,那咱们就来看看Entry的结构:

 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;
	}
}

从上述代码可以看出,Entry有一个next属性指向下一个Entry实体,咱们可以将其理解为链表结构。整个HashMap类似如此结构:

众说周知,java的每一个对象均有一个hashCode方法,调用该方法可以活得对象的散列值,HashMap内部通过以下代码获取需要存储的对象应该存储在Entry[] table的哪个位置:

  int hash = hash(key.hashCode());

4、HashMap的put操作:

 整个put操作基本可以分为三个步骤:

获取存储数组索引位置。

遍历特定位置上的Entry链表,是否已经存在key值一样的数据,有则覆盖value值,完成put操作。

向特定位置上的Entry链表添加新put数据,注意,在链表表头添加。具体实现代码如下:

 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;
            }
        }

----------------以上为第二步骤代码,根据以上代码也就解释了为什么put的时候key一样,value值会被最新的值覆盖掉。通过equals方法判断两个key值是否相等,如果执行了覆盖,则不执行第三步操作。-------------------------

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

--------------以上为第三步骤代码,可以看出,新增了一个Entry实体,并将其next属性指向了操作之前的链表头元素,相当于将新的Entry实体加入到了链表头部--------------

5、HashMap的get操作-- (xhEditor不知道怎么回事,不能选中文字改变字体颜色了。。。):

    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方法的第一二步类似,首先找到Entry数组索引,再根据key值来获取value值,也是通过key的equals来判断。

6、总结:

HashMap为何可以快速根据key定位到value值,其实就是通过key的hashCode方法快速找到了value值所在的Entry链表,只需要在定位后的Entry链表中寻找value值就可以了,不需要全局全量定位,大大提高了操作效率。

其实个人认为可能HashMap是这种结构有如下原因:

数组本来就是寻址容易,操作困难,包括插入和删除。

链表本来就是寻址困难,操作容易。

HashMap就是用数组来寻找再用链表来进行操作,使二者优势结合。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值