原文链接:https://www.changxuan.top/?p=1208


前两天,我在一位同学提交中看到了下面这样的一行代码,让我很是惊讶。

Map<String, String> temp = new HashMap<>(6);

我给他说,你这样实例化 Map 对象不好用,他不服气。我说小朋友:如果想指定 HashMap 对象的容量得用2的N次方。他说你这也没用。我说,我这个有用,这样才能充分利用分配的内存空间。他非和我试试,我说可以,不过得先一起看看源码。

什么是HashMap?

在弄懂标题的问题之前,首先需要清楚 HashMap 的概念。HashMap 是基于哈希表的 Map 接口的实现,线程不安全,且不保证映射顺序。

HashMap 存储数据依赖的是数组和[链表|红黑树],具体链表和红黑树之间如何转换的细节此文不做详细介绍。而本文开头提到的实例化容量大小指的则是数组的大小。

如何计算元素在数组中所对应的下标?

首先计算元素的哈希值,方法如下:

static final int hash(Object key) {
        int h;
     // h = key.hashCode();
     // h = h ^ (h >>> 16)
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

为什么不直接使用 key.hashCode()的值,我们后面会提到。

计算出来哈希值后,由于数组容量相对来说较小肯定不能直接使用哈希值当作索引值。所以需要使用哈希值对数组长度减一后的值取模。不过在在 HashMap 中可不是直接使用 % 运算符来操作的。为了提高效率,采用的是与运算的方式,代码如下:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
      // n 为数组容量, (n-1) & hash 则是计算索引值
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
          ... ...
        }
}

既然清楚了计算元算在数组中所对应下标的方法,那么证明为什么实例化 HashMap 对象的容量要使用2的N次方就简单多了。

假如初始容量为2的3次方数字8,当哈希值与容量大小减一的值进行与运算时可以保证结果比较均匀的分布在数组上。

  10100101 11000100 00100101
& 00000000 00000000 00000111 // 7
----------------------------------
  00000000 00000000 00000101 // 结果可以是[0,7]中的任一数字

如果初始容量为6,那么出现哈希冲突的几率就会增加了。

  10100101 11000100 00100101
& 00000000 00000000 00000101 // 5 
----------------------------------
  00000000 00000000 00000101 // 5
  
  10100101 11000100 00100111
& 00000000 00000000 00000101 // 5 
----------------------------------
  00000000 00000000 00000101 // 5

如果下面的值低位全是1,那么上面的这次哈希冲突则可以避免。那么你想想,假如指定的容量大小为5又会怎么样呢?其实2的N次方数字-1的二进制形式这个特性在好多地方会很好用,可以在小本本记上。

哦,前面说为什么计算出来的散列值需要再让高16位和低十六位做异或运算,主要是让参与与运算的位同时具有高位和低位的特征,来减少哈希碰撞次数。

小朋友,还试不试啦!

为什么 HashMap 的容量大小要设置为2的N次方?的更多相关文章

  1. HashMap的容量大小增长原理(JDK1.6/1.7/1.8)

    . 前言 HashMap的容量大小会根据其存储数据的数量多少而自动扩充,即当HashMap存储数据的数量到达一个阈值(threshold)时,再往里面增加数据,便可能会扩充HashMap的容量. 可能 ...

  2. 关于HashMap初始化容量问题

    使用阿里云代码规范插件扫描后出现以下提示: hashmap should set a size when initalizing,即hashmap应该在初始化时设置一个大小 在网上搜到一篇讲解(htt ...

  3. 我说我了解集合类,面试官竟然问我为啥HashMap的负载因子不设置成1!?

    在Java基础中,集合类是很关键的一块知识点,也是日常开发的时候经常会用到的.比如List.Map这些在代码中也是很常见的. 个人认为,关于HashMap的实现,JDK的工程师其实是做了很多优化的,要 ...

  4. HashMap初始化容量过程

    集合是Java开发日常开发中经常会使用到的,而作为一种典型的K-V结构的数据结构,HashMap对于Java开发者一定不陌生.在日常开发中,我们经常会像如下方式以下创建一个HashMap: Map&l ...

  5. 我说HashMap初始容量是16,面试官让我回去等通知

    众所周知HashMap是工作和面试中最常遇到的数据类型,但很多人对HashMap的知识止步于会用的程度,对它的底层实现原理一知半解,了解过很多HashMap的知识点,却都是散乱不成体系,今天一灯带你一 ...

  6. 【转载】VMware虚拟机修改硬盘容量大小

    很多人在安装虚拟机系统的时候,为了节省硬盘空间,把硬盘容量设置得较小,可是后来发现硬盘容量不够用了.在VMware中又不能直接修改虚拟机的硬盘容量大小,或者重建虚拟机系统,非常麻烦. 其实在VMwar ...

  7. jdk1.8 HashMap底层数据结构:深入解析为什么jdk1.8 HashMap的容量一定要是2的n次幂

    前言 1.本文根据jdk1.8源码来分析HashMap的容量取值问题: 2.本文有做 jdk1.8 HashMap.resize()扩容方法的源码解析:见下文“一.3.扩容:同样需要保证扩容后的容量是 ...

  8. 为什么jdk1.8 HashMap的容量一定要是2的n次幂

    一.jdk1.8中,对“HashMap的容量一定要是2的n次幂”做了严格控制 1.默认初始容量: [Java] 纯文本查看 复制代码 ? 1 2 3 4 /**  * The default init ...

  9. 根据屏幕大小动态设置字体rem

    1.根据屏幕大小动态设置字体rem var docEl = document.documentElement, //当设备的方向变化(设备横向持或纵向持)此事件被触发.绑定此事件时, //注意现在当浏 ...

随机推荐

  1. iMindMap:一款可以免费试用的思维导图软件

    思维导图在我们的生活中已经越来越常见了,在市场上也有很多思维导图的软件.今天小编给大家推荐的是一款免费思维导图软件-iMindMap.它具有22种布局模式,包含组织架构图.鱼骨图.时间线.气泡图等,3 ...

  2. guitar pro 系列教程(六):Guitar Pro音频导出功能之RSE音源

    让我们继续进行guitar pro的教程 上一章节,我们讲解了guitar Pro的播放与显示功能,在Guita pro的音源选择中分为两类,一种是自带的RES高保真音源,一种是MIDI输入音源.如果 ...

  3. starUML软件破解

      下载链接:http://pan.baidu.com/s/1bpnHJ8F 密码:hk3x 1.使用Editplus或者Notepad++等特殊的文本编辑器打开%StarUML_HOME%/www/ ...

  4. C语言讲义——字符串库函数

    字符串库函数<string.h> 求字符串长度(不含结束符'\0'****) strlen(str) 字符串赋值(可能造成数组越界) strcpy(str," 水浒传 " ...

  5. Java蓝桥杯练习——杨辉三角形

    问题描述: 杨辉三角形又称Pascal三角形,它的第i+1行是(a+b)i的展开式的系数. 它的一个重要性质是:三角形中的每个数字等于它两肩上的数字相加. 下面给出了杨辉三角形的前4行: 1 1 1 ...

  6. mqProducer

    producer核心属性:生产者所属组,消息服务器在回查事物状态时会随机选择该组中任何一个生产者发起事务回查请求. createTopicKey:默认topicKey defaultTopicQueu ...

  7. C# WinForm UDP 发送和接收消息

    using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; ...

  8. IAR编译出现Configuration is up-to-date.

    IAR编译出现如下: Building configuration: SimpleBLECentral - CC2541EM Updating build tree... Configuration ...

  9. 第14.6节 使用Python urllib.request模拟浏览器访问网页的实现代码

    Python要访问一个网页并读取网页内容非常简单,在利用<第14.5节 利用浏览器获取的http信息构造Python网页访问的http请求头>的方法构建了请求http报文的请求头情况下,使 ...

  10. Monkey 部署环境

    Monkey的介绍 它是Android系统自带一个命令行工具,可以运行在模拟器里或者真是设备中运行. Monkey是发送伪随机用户事件的工具. Monkey向系统发送伪随机的用户事件流,实现对正在开发 ...