在平常的开发当中,HashMap是我最常用的Map类(没有之一),它支持null键和null值,是绝大部分利用键值对存取场景的首选。需要切记的一点是——HashMap不是线程安全的数据结构,所以不要在多线程场景中应用它。

通常情况下,我们使用Map的主要目的是用来放入(put)、访问(get)或者删除(remove),而对顺序没有特别的要求——HashMap在这种情况下就是最好的选择。

01、Hash

对于HashMap来说,难理解的不在于Map,而在于Hash。

Hash,一般译作“散列”,也有直接音译为“哈希”的,这玩意什么意思呢?就是把任意长度的数据通过一种算法映射到固定长度的域上(散列值)。

再直观一点,就是对一串数据wang进行杂糅,输出另外一段固定长度的数据er——作为数据wang的特征。我们通常用一串指纹来映射某一个人,别小瞧手指头那么大点的指纹,在你所处的范围内很难找出第二个和你相同的(人的散列算法也好厉害,有没有?)。

对于任意两个不同的数据块,其散列值相同的可能性极小,也就是说,对于一个给定的数据块,找到和它散列值相同的数据块极为困难。再者,对于一个数据块,哪怕只改动它的一个比特位,其散列值的改动也会非常的大——这正是Hash存在的价值!

在Java中,String字符串的散列值计算方法如下:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;         for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

看得懂看不懂都没关系,我们就当是一个“乘加迭代运算”的算法。借此机会,我们来看一下“沉”、“默”、“王”、“二”四个字符串的散列值是多少。

String[] cmower = { "沉", "默", "王", "二" };
for (String s : cmower) {
    System.out.println(s.hashCode());
}

输出的结果如下(5位数字):

沉的散列值:27785
默的散列值:40664
王的散列值:29579
二的散列值:20108

对于HashMap来说,Hash(key,键位)存在的目的是为了加速键值对的查找(你想,如果电话薄不是按照人名的首字母排列的话,找一个人该多困难「我的微信好友有不少在昵称前加了A,好狠」)。通常情况下,我们习惯使用String字符串来作为Map的键,请看以下代码:

Map<String, String> map = new HashMap<>();
String[] cmower = { "沉", "默", "王", "二" };
for (String s : cmower) {
    map.put(s, s + "月入25万");
}

那HashMap会真的会将String字符串作为实际的键吗?我们来看HashMap的put方法源码:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

虽然只有一个putVal()方法的调用,但是你应该已经发现,HashMap内部会把key进行一个hash运算,具体代码如下:

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

假如key是String字符串的话,hash()会先获取字符串的hashCode(散列值),再对散列值进行位于运算,最终的值为HashMap实际的键(int值)。

既然HashMap在put的时候使用键的散列值作为实际的键,那么在根据键获取值的时候,自然也要先对get(key)方法的key进行hash运算,请看以下代码:

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

02、散列值冲突怎么解决

尽管散列值很难重复,我们还是要明白,这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出。

也就是说,key1 ≠ key2,但function(key1)有可能等于function(key2)——散列值冲突了。怎么办?

最容易想到的解决办法就是:当关键字key2的散列值value与key1的散列值value出现冲突时,以value为基础,产生另一个散列值value1,如果value1与value不再冲突,则将value1作为key2的散列值。

依照这个办法,总会找到不冲突的那个。

03、初始容量和负载因子

HashMap的构造方法主要有三种:

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
} public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
} public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
}

其中initialCapacity为初始容量(默认为1 << 4 = 16),loadFactor为负载因子(默认为0.75)。初始容量是HashMap在创建时的容量(HashMap中桶的数量);负载因子是HashMap在其容量自动增加之前可以达到多满的一种尺度。

当HashMap中的条目数超出了负载因子与当前容量的乘积时,则要对HashMap扩容,增加大约两倍的桶数。

通常,默认的负载因子 (0.75) 是时间和空间成本上的一种折衷。负载因子过高虽然减少了空间开销,但同时增加了查询成本。如果负载因子过小,则初始容量要增大,否则会导致频繁的扩容。

在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少扩容的操作次数。

如果能够提前预知要存取的键值对数量的话,可以考虑设置合适的初始容量(大于“预估元素数量 / 负载因子”,并且是2的幂数)。

04、小结

在之前很长的一段时间内,我对HashMap的认知仅限于会用它的put(key, value)value = get(key)

但,当我强迫自己每周要输出一篇Java方面的技术文章后,我对HashMap真的“深入浅出”了——散列值(哈希值)、散列冲突(哈希冲突)、初始容量和负载因子,竟然能站在我面前一直笑——而原先,我见到这些关键字就逃之夭夭了,我怕见到它们。


上一篇:Java 集合类入门篇

下一篇:Java泛型的重要目的:别让猫别站在狗队里

深入浅出一下Java的HashMap的更多相关文章

  1. java:警告:[unchecked] 对作为普通类型 java.util.HashMap 的成员的put(K,V) 的调用未经检查

    java:警告:[unchecked] 对作为普通类型 java.util.HashMap 的成员的put(K,V) 的调用未经检查 一.问题:学习HashMap时候,我做了这样一个程序: impor ...

  2. angularJS操作键值对象(类似java的hashmap)填坑小结

    前言: 我们知道java的hashmap中使用最多的是put(...),get(...)以及remove()方法,那么在angularJS中如何创造(使用)这样一个对象呢 思路分析: 我们知道在jav ...

  3. Java学习——HashMap

    遍历 Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map. ...

  4. Java之HashMap在多线程情况下导致死循环的问题

    PS:不得不说Java编程思想这本书是真心强大.. 学习内容: 1.HashMap<K,V>在多线程的情况下出现的死循环现象   当初学Java的时候只是知道HashMap<K,V& ...

  5. Mabitis 多表查询(一)resultType=“java.util.hashMap”

    1.进行单表查询的时候,xml标签的写法如下 进行多表查询,且无确定返回类型时 xml标签写法如下: <select id="Volume" parameterType=&q ...

  6. 解决Apache CXF 不支持传递java.sql.Timestamp和java.util.HashMap类型问题

    在项目中使用Apache开源的Services Framework CXF来发布WebService,CXF能够很简洁与Spring Framework 集成在一起,在发布WebService的过程中 ...

  7. Java中HashMap遍历的两种方式

    Java中HashMap遍历的两种方式 转]Java中HashMap遍历的两种方式原文地址: http://www.javaweb.cc/language/java/032291.shtml 第一种: ...

  8. 简单分析Java的HashMap.entrySet()的实现

    关于Java的HashMap.entrySet(),文档是这样描述的:这个方法返回一个Set,这个Set是HashMap的视图,对Map的操作会在Set上反映出来,反过来也是.原文是 Returns ...

  9. java中HashMap的用法

    重点介绍HashMap.首先介绍一下什么是Map.在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value.在下文中会 ...

随机推荐

  1. 如何查找Linux服务器上JDK安装路径?

    成功远程到你要部署软件的Linux服务器上.这是第一步. 查看JDK版本:java -version 查看java执行路径:which java 查看JAVA_HOME路径:echo $JAVA_HO ...

  2. Ubuntu基础教程——安装谷歌Chrome浏览器

    对于刚刚开始使用Ubuntu并想安装谷歌Chrome浏览器的新用户来说,本文所介绍的方法是最快捷的.在Ubuntu上安装谷歌Chrome的方法有很多.一些用户喜欢直接在 谷歌Chrome下载页面 获得 ...

  3. 怎样在Ubuntu中设置环境变量

    首先启动终端. 单击屏幕左上角的Ubuntu图标,在弹出的窗口中点击搜索栏,输入"terminal", 稍等片刻,终端就会赫然在目!二话不说,直接点击!     然后打开环境设置文 ...

  4. Java与Kotlin, 哪个是开发安卓应用的首选语言?

    Java是很多开发者创建安卓应用的首选语言.但它在 Android 界的领导地位正受到各种新语言的挑战,Kotlin就是其一.虽然Kotlin最近才开始受到热捧,但有为数不少的人相信 Kotlin 在 ...

  5. Python 文件读写的三种模式和区别

    #coding=utf-8 #__author:Administrator #__time:2018/5/9 13:14 #__file_name:text1 import io #能调用方法的一定是 ...

  6. 转载:python + requests实现的接口自动化框架详细教程

    转自https://my.oschina.net/u/3041656/blog/820023 摘要: python + requests实现的接口自动化框架详细教程 前段时间由于公司测试方向的转型,由 ...

  7. 学习笔记1--响应式网页+Bootstrap起步+全局CSS样式

    一.学习之前要了解一些背景知识: 在2g时代,3g时代,4g时代,早期的网页浏览设备,功能机,智能机.(本人最喜欢的透明肌,和古典黑莓机) 1.什么是响应式网页? Responsive Web Pag ...

  8. MAC下Intellij IDEA常用快捷键

    alt+f7 : 查找在哪里使用 command+alt+f7 : 这个是查找选中的字符在工程中出现的地方,可以不是方法变量类等,这个和上面的有区别的 command+F7 : 可以查询当前元素在当前 ...

  9. 【费马小定理】BZOJ3260 跳

    Description 从(0,0)走到(n,m),没走过一个点(x,y)贡献为C(x,y),求最小贡献和. Solution 让我们guess一下 走的路线一定是先走长的一边再走短的一边,两条直线 ...

  10. 【建模+强连通分量】POJ1904 King's Quest

    Description 一个国王有n个王子,同时有n个女孩.每个王子都有自己喜欢的若干个女孩,现给定一个合法的完备匹配(也就是一个王子娶其中一个自己喜欢女孩),求每个王子可以选择哪些女孩可以让剩下的每 ...