HashMap学习总结

HashMap学习总结

基本概述

数据结构组成:数组 + 链表

HashMap的实现是基于哈希表的Map接口实现的非同步实现,特点:允许使用null键 和null值,但是在hashmap中是不保证顺序,即每个键值对没有按某种功能排序

从源码分析来看,HashMap的构造函数中创建了一个 Entry 的数组,这个Entry 在源码中是个静态定义的类,说明,在数组table 中的每一项数据就是一个 Entry类型的数据,而这个Entry的部分定义如下:

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

可以看出这个Entry基本就是由 键值对+next指针组成,这也就证实了HashMap的数据结构组成为数组加链表。

方法解读

存储即PUT方法

首先解释一下HashMap中数据进来如何进行保存的。

当我们把键值对put进的时候,首先会对key的hashCode计算hash值,计算出来的hash值就类似一个地址坐标,大概能确定出这个键值对会被保存在数组的位置,但是数组的一个下标的位置是可以存在多个键值对的,所以,多个键值对在同一个数组的一个下标下构成了一个链表结构,按照规定,新加入的放在链表的头部,最早加入的在链表的尾。

在put的时候,分两种种情况。

1.如果key值不存在,即推断出这个键值对是新,可以直接直接加入进来,返回值为null;

2.如果key已经存在,就是对原有value的一次覆盖,返回值是原value值;

查找中巧妙构造

现在已经知道了HashMap中,每个Entry对象根据自身hashcode计算出的值,放在数组中指定的位置,所以在查找的时候就有迹可循了。

想法一就是自然的想到:根据数组的长度length,可以进行模运算,这样每次用 h%length 就可以找到指定的位置。而且数据的分布也会变得比较均匀,但是实际情况由于“模运算”需要较大的消耗,所以在HashMap使用的是另外的方法。

实际使用的方法:在HashMap中有一个方法indexFor(int h, int length),该方法放回的值就是需要保存的数据应该被放在table中索引的地址,它的方法如下:

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

通过代码我们可以发现 实际寻址我们使用的计算公式是 h & (length-1);此处是一个“与”运算。这个方法在这里是十分巧妙的,原因在与这个 length是HashMap的长度,即容量,在HashMap在初始化的时候,它的的底层数组的长度总是2的n次方!!!引入HashMap构造器中代码:

// Find a power of 2 >= initialCapacity
int capacity = 1;
    while (capacity < initialCapacity)  
        capacity <<= 1;

所以可以转化成: h & (length-1)的本质 等价变成了 h%length,这样形成了和上午中提到的“模运算”一样的结果,数据分布变得十分均匀。

所以整个PUT的过程如下:当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部。

读取

读取也是建立刚才查找的基础上实现的,先通过key的HashCode,找到数组的对应下标,然后在下标所在的链表中,不断使用equal方法,比较key值直到找到 那个Entry对象。

HashMap的扩容

从在实际的使用中我们就发现了,HashMap与正常使用的数组不同,他不是一个静态的引擎,他是可以自动扩充的,这点和ArrList很相似。但是在HashMap中,扩容就不是简单的在原数据空间再加一块存储区域,。上文提及到了索引的作用,所以在HashMap扩容之后,所有的Entry对象都要重新计算索引地址,从而放在正确的位置,而这个重新计算索引位置,并重新放入新的位置;这个过程是非常消耗性能的。

扩容的相关规则:扩容的目的是为了提高查询的效率,因为当数据量太大时,链表的的长度过长,这样会大大影响查询的速度。扩容的临界点:为当前元素的个数到达当前HashMap长度的0.75时,将会扩容成原来的两倍。

Fail-Fast机制

Fail-Fast是一种错误检测机制,他的作用是针对HashMap线程不安全的特点,如果在迭代器使用过程中,其他的线程了修改了map,这样就会抛出ConcurrentModificationException。

目的就是为了避免多个线程同时对一个集合进行操作,并且其中还包含了修改的操作的情况。

而实现之中错误检测机制的源码可以了解其检测的方法:通过一个modCount域,来判断,modCount域 在迭代器初始化的时候会传值,在使用迭代器期间,只要有和modify有关的操作,modcount会++,这样最后比较modcount和expectedModCount的值是否相等就可以判断是否有别的线程修改了map。

PS

在学习了HashMap的内容之后,我们了解了有关其底层的实现机制后,在以后遍历hashmap的时候,尽量使用

Map map = new HashMap();
  Iterator iter = map.entrySet().iterator();

而并非

 Map map = new HashMap();
  Iterator iter = map.keySet().iterator();

因为底层数组table存储的就是Entry对象,而如果同过key遍历,还有个先把key和Entry的映射过程,所以第一种方法的消耗时间大大小于后者。

引用连接:http://wiki.jikexueyuan.com/project/java-collection/hashmap.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值