Java集合之HashMap总结

一、HashMap 概述


        摘自JKDAPI-1.6版本部分解释: HashMap 是基于哈希表的 Map 接口的实现。先解释一下哈希表,来自百度百科。哈希表又叫散列表,是根据关键码值(key,value)直接进行访问的数据结构,也就是说,它通过把关键码映射到表中的一个位置,以加快查找的速度。这个映射函数叫做散列函数,存放记录的 数组叫做散列表。HashMap 不允许重复的 Key ,单允许重复的 value ,允许 null 值和 null key,它是无序的集合容器。 HashMap 的实例有两个参数影响其性能: 初始容量加载因子。容量(不是初始容量)是哈希表中元素(桶)的数量,初始容量是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目超出了加载因子与当前容量的乘积时,则要对该哈希表进行扩容,扩容和 ArrayList 一样,创建新长度的哈希表,将旧的哈希表复制到新哈希表中去,不同的是哈希表的映射关系要重建,每次扩容哈希表的长度为原初始容量乘以 2 。HashMap 不是同步的。

二、总结 HashMap 的关键点


        1、HashMap 是哈希表 + 单向链表实现的。

        2、HashMap 的迭代效率跟初始容量和加载因子有关。

        3、HashMap 每次扩容原长度乘以 2 。

三、结论分析


        1、为什么 HashMap 是哈希表 + 单向链表实现的?

                哈希表中储存的元素是 Map.Entry 类型的实例,其属性包含 key、value、next、hash。前面讲过 ArrayList 底层是数组实现的,储存每个元素是是在列表的最后或者指定索引去存储数据。虽然哈希表也是数组,但是储存数据的位置并不固定,它是由 key 值计算出一个 hashCode 值,在通过这个值 & (初始容量 - 1)得到的索引,这个计算结果并不是唯一的。假设存储一个元素应该放在第 5 位,另一个元素计算结果也需要放在第 5 为,这样就产生了“哈希碰撞”。为了解决这种冲突,在该索引的元素 Entry 储存了 next 成员属性,新存入的元素被放在哈希表中,而已经在该位置上的元素被指向了新元素的 next 属性中,行成了一条链。在 LinkedList 的总结中我们说过,双向链表是可以找下一个、前一个元素的,而这里只能找下一个元素,如果能理解双向链表,那么这里的单向链表也一目了然了。 综上所述,之所以采用哈希表 + 单向链表的方式实现,是因为 key 计算出来的数组索引和其他 key 可能会产生冲突,为了解决该冲突采用此类方式解决。在JDK-1.8版本中,HashMap 采用的是哈希表 + 平衡数的方式解决的哈希碰撞,仅仅作为了解。

        2、为什么 HashMap 每次扩容原长度是乘以 2 ?

                HashMap 的无参构造函数指定了默认初始容量为16,默认加载因子为0.75,其他的构造器用户可以指定容量和初始因子,看一下代码:

    public HashMap(int initialCapacity, float loadFactor) {
        // 如果指定的初始容量小于 0 ,抛异常,非法参数
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
        // 限制 HashMap 的最大初始容量是 2 的 30 次方
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;

        // 检查加载因子参数的合法性
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);

        // 将指定的容量转换成 2 的幂次方
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        // 初始化加载因子
        this.loadFactor = loadFactor;
        // 初始化临界值
        threshold = (int) (capacity * loadFactor);
        // 按照计算出的容量初始化哈希表
        table = new Entry[capacity];
        init();
    }

                构造方法中无论你传入的指定初始容量是多少,都会将它转成2的幂次方。因为最终计算哈希桶存入哈希表中的位数是通过 哈希码 & 哈希表的初始容量减一,如果哈希表的初始容量是 2 的幂次方,方便让哈希桶更散,哈希碰撞的几率更小。Java 的 & 允许是两个二进制的数,只有这两个位数上的数字都是 1 ,才是 1,否则就是 0,如果不是 2 的幂次方 - 1 这种算法,有些位置上永远都是 0,导致浪费哈希表的空间。

        3、为什么 HashMap 的使用要重写 equals 和 hashCode 方法?

                在 HashMap 进行 put 存入数据时,根据 key 值的 hashCode 方法获取哈希码,&上哈希表的长度减一计算出在哈希表中的位置,之后再调用 key 的 equals 方法判断在计算出的索引位置上已有的 key 和 put 的 key 是否相等,如果相等则覆盖,否则存入链表。hashCode、equals 方法一个决定了元素存入哈希表的位置,一个决定了元素是否要被更新。如果在开发中不重写 equalse 和 hashCode 的方法,又使用自己定义的对象作为 key 时,则由可能导致业务错误。

四、总结


        HashMap 是我们工作中常用的容器,它底层是数组 + 链表实现的,当然这个数组是哈希表。但只要是数组结构的容器,那么就和 ArrayList 一样,它会扩容,影响到了效率。那么我们在实际业务中如果能预先知道储存的数量,就可以减少扩容的次数, HashMap 储存的数量是哈希表的长度(初始容量)乘以加载因子,如果初始容量设置为 1000,加载因子为1,那么在储存第 1001 个元素的之前,HashMap 将自动扩容。
        根据上面的分析,我们可以得知,如果你需要存储键值对(key-value)的数据,它允许 null 键、null 值、key 唯一、无序的情况下,应该优先选择 HashMap,在使用前如果能够知道具体存入的元素数量,应该设置好初始容量和加载因子。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值