十个问题带你了解和掌握java HashMap

一、前言

本篇内容是源于 “ 由阿里巴巴Java开发规约HashMap条目引发的故事”,并在此基础上加了自己的对HashMap更多的思考认识和整理。并且作为一名java开发工程师,应该是要了解和掌握的这些知识!

  • 在《阿里巴巴java开发规约中》提到:

【推荐】集合初始化时,指定集合初始值大小。

说明:HashMap使用如下构造方法进行初始化,如果暂时无法确定集合大小,那么指定默认值(16)即可!

在进行本篇的阅读之前,首先请你花三分钟时间,思考面关于HashMap的十个问题,带着问题去阅读内容效果更好!

问题如下:

  1. 1.HashMap 是什么,实现原理?
  2. 2.HashMap 默认bucket(桶)数组多大?(上面已经给出),最大容量是多少?
  3. 3.如果new HashMap<>(19),bucket数组多大?
  4. 4.HashMap 什么时候开辟bucket数组占用内存?
  5. 5.HashMap 何时扩容?
  6. 6.为什么String, Interger这样的包装类类适合作为HashMapkey(键)呢?
  7. 7.如果用自定义对象当做hashmapkey进行存储要注意什么?
  8. 8.当两个对象的hashcode相同会发生什么(如何解决hash冲突)?如果两个键的hashcode相同,你如何获取值对象?
  9. 9.HashMap ConcurrentHashMap的区别?
  10. 10.jdk1.7jdk1.8HashMap的实现有哪些区别?

二:HashMap相关知识的整理和简单介绍

HashMap是基于哈希表的Map实现的,一个Key对应一个Value,允许使用null键和null值,不保证映射的顺序,特别是它不保证该顺序恒久不变!是非线程安全的的。

其中 “不保证映射的顺序,特别是它不保证该顺序恒久不变” 如何理解?


  1. 当哈希表中的条目数超出了当前容量与负载因子的乘积( Capacity * LoadFactor)时的时候,哈希表进行rehash操作(即重建内部数据结构),此时映射顺序可能会被打乱!

1.HashMap 是什么,实现原理?

HashMap是一个存储key和value的集合,一个key对应一个value,实现原理是使用hash算法通过对key进行hash后存储哈希表(也称为哈希数组)中,哈希表(哈希数组)的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中。

如果容量不足(超过了阀值)时,同样会自动增长

看下图(jDK1.7):

其中哈希表(哈希数组)和 单链表的节点元素

2.HashMap 默认bucket(桶)数组多大?(上面已经给出),最大容量是多少?

  1. // 默认的初始容量(容量为HashMap中槽的数目)是16,且实际容量必须是2的整数次幂。
  2. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 Capacity
  3. // 默认加载因子为0.75
  4. static final float DEFAULT_LOAD_FACTOR = 0.75f; LoadFactor
  5. public HashMap() {
  6. this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
  7. }
  8. // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
  9. static final int MAXIMUM_CAPACITY = 1 << 30;

总结: 默认值初始值为16,最大值2 的30次方。

3.如果new HashMap<>(19),bucket数组多大?

HashMap 的 bucket 数组大小一定是2的幂,如果 new 的时候指定了容量且不是2的幂,

实际容量会是最接近(大于)指定容量的2的幂,比如 new HashMap<>(19),比19大且最接近的2的幂是32,实际容量就是32。

  1. //jdk1.7
  2. private void inflateTable(int toSize) {
  3. // Find a power of 2 >= toSize, 2的幂 >= toSize
  4. int capacity = roundUpToPowerOf2(toSize); //计算一定为2的幂
  5. threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
  6. table = new Entry[capacity];
  7. initHashSeedAsNeeded(capacity);
  8. }

4.HashMap 什么时候开辟bucket数组占用内存?

HashMap 在 new 后并不会立即分配bucket数组,而是第一次 put 时初始化**使用resize() 函数进行分配。(类似 ArrayList 在第一次 add 时分配空间)

5.HashMap 何时扩容?

数据 put 后,如果数据量超过threshold( Capacity * LoadFactor ),就要resize!

  1. //jdk1.7
  2. void addEntry(int hash, K key, V value, int bucketIndex) {
  3. //每次加入键值对时,都要判断当前已用的size是否大于等于threshold(阀值),如果大于等于,则进行扩容,将容量扩为原来容量的2倍。
  4. if ((size >= threshold) && (null != table[bucketIndex])) {
  5. resize(2 * table.length);
  6. hash = (null != key) ? hash(key) : 0;
  7. bucketIndex = indexFor(hash, table.length);
  8. }
  9. createEntry(hash, key, value, bucketIndex);
  10. }

resize()方法进行扩容,扩容是一个相当耗时的操作,因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。(具体可以看源码,jdk1.8进行相应的优化)

在用HashMap的时,如果能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能。

6.为什么String, Interger这样的包装类类适合作为HashMap的key(键)呢?

String, Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。

其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。

如果你可以仅仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。

如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。

7.如果用自定义对象当做hashmap的key进行存储要注意什么?

这是问题6的延伸。如果一个自定义对象做为key,一定要注意对象的不可变性,否则可能导致存入Map中的数据无法取出,造成内存泄漏!

(1).要注意这个对象是否为可变对象。

(2).一定要重写hashcode方法和equals方法,因为在HashMap的源代码里面,是先比较HashCode是否相等,同时要满足引用相等或者equals相等。

可参考:危险!在HashMap中将可变对象用作Key

8.当两个对象的hashcode相同会发生什么(如何解决hash冲突)?如果两个键的hashcode相同,你如何获取值对象?

两个对象hashcode相同,它们在的哈希bucket中找到了相同位置,会发生“碰撞”。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。可以参考问题1中的图!

当我们调用get()方法,HashMap会使用key的hashcode找到bucket位置,然后发现两个对象存储在一个哈希bucket中,找到bucket位置之后,会调用key.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

9.HashMap 和 ConcurrentHashMap的区别?

说简单点就是HashMap是线程不安全的,单线程情况下使用;而ConcurrentHashMap是线程安全的,多线程使用!

可以使用 Collections.synchronizedMap(new HashMap<String, Integer>());将HashMap封装成线程安全的,其内部实现原理是使用了关键字synchronized。

10.jdk1.7和jdk1.8中HashMap的实现有哪些区别?

jdk1.7和jdk1.8的区别还是很多,下面介绍两个!

(1):存储结构

如图(jDK1.8)

  1. jdk1.7 static class Entry<K,V> implements Map.Entry<K,V> {
  2. jdk1.8 static class Node<K,V> implements Map.Entry<K,V> {
  3. jdk7内部使用使用Entry<K,V>而jdk1.8内部使用Node<K,V>,都是实现Map.Entry<K,V> ,最主要的区别就是列表长度大于8时转为红黑树!

在JDK1.7版本中.不管负载因子和Hash算法设计的再合理,也免不了会出现拉链(单链表)过长的情况,一旦出现拉链(单链表)过长,会严重影响HashMap的性能。

在JDK1.8版本中,对数据结构做了进一步的优化,引入了红黑树。而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。本文不再对红黑树展开讨论,

想了解更多红黑树数据结构的工作原理可以参考 红黑树数据结构的工作原理

总结:

JDK7 中的 HashMap 采用数组+链表的结构来存储数据。

JDK8 中的 HashMap 采用数组+链表或红黑树的结构来存储数据。

(2):一些操作方法的优化如resize

resize()用来第一次初始化,或者 put 之后数据超过了threshold(Capacity * LoadFactor)后扩容,这里具体不贴代码了,大概说明一下!

jdk1.7 直接扩容两倍,table.length * 2; 源码中使用resize(2 * table.length);

jdk1.8 优化数组下标计算: index = (table.length - 1) & hash ,由于 table.length 也就是capacity 肯定是2的N次方,使用 & 位运算意味着只是多了最高位, 这样就不用重新计算 index,元素要么在原位置,要么在原位置+ oldCapacity

如果上面内容哪里有问题欢迎指出!或者你对上面的内容有自己的认识和理解也欢迎评论,希望互相沟通,共同成长!谢谢!

三:参考的博文

由阿里巴巴Java开发规约HashMap条目引发的故事

java集合系列——Map之HashMap介绍(八)

HashMap的工作原理

http://blog.csdn.net/ns_code/article/details/36034955

Java8系列之重新认识HashMap

四:更多知识学习

最后在推广一个我整理的java知识点,目录如下!有兴趣的可以点击阅读阅读一下!

java的线程安全、单例模式、JVM内存结构等知识学习和整理


**如果您觉得这篇博文对你有帮助,请点个赞,谢谢!**

**如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!**


欢迎访问我的csdn博客,我们一同成长!

"不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!"

博客首页:http://blog.csdn.net/u010648555

十个问题带你了解和掌握java HashMap的更多相关文章

  1. Java 打印金字塔 or 打印带数字的金字塔 (Java 学习中的小记录)

    Java 打印金字塔 or 打印带数字的金字塔 (Java 学习中的小记录) 作者:王可利(Star·星星) 效果图: 代码如下: class Star8 { public static void m ...

  2. atitit. 文件上传带进度条 atiUP 设计 java c# php

    atitit. 文件上传带进度条 atiUP 设计 java c# php 1. 设计要求 1 2. 原理and 架构 1 3. ui 2 4. spring mvc 2 5. springMVC.x ...

  3. 用java自带jdk开发第一个java程序

    [学习笔记] 1.用java自带jdk开发第一个java程序:   下面要讲的eclipse要想正常工作,需要先学会配置这里的jdk.jdk要想正常工作,需先学会配置JAVA_HOME和ClassPa ...

  4. 带你全面了解高级 Java 面试中需要掌握的 JVM 知识点

    目录 JVM 内存划分与内存溢出异常 垃圾回收算法与收集器 虚拟机中的类加载机制 Java 内存模型与线程 虚拟机性能监控与故障处理工具 参考 带你全面了解高级 Java 面试中需要掌握的 JVM 知 ...

  5. [翻译]Java HashMap工作原理

    大部分Java开发者都在使用Map,特别是HashMap.HashMap是一种简单但强大的方式去存储和获取数据.但有多少开发者知道HashMap内部如何工作呢?几天前,我阅读了java.util.Ha ...

  6. Java学习笔记(二二)——Java HashMap

    [前面的话] 早上起来好瞌睡哈,最近要注意一样作息状态.       HashMap好好学习一下. [定义] Hashmap:是一个散列表,它存储的内容是键值对(key——value)映射.允许nul ...

  7. java集合框架之java HashMap代码解析

     java集合框架之java HashMap代码解析 文章Java集合框架综述后,具体集合类的代码,首先以既熟悉又陌生的HashMap开始. 源自http://www.codeceo.com/arti ...

  8. HashMap的原理与实 无锁队列的实现Java HashMap的死循环 red black tree

    http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html https://zh.wikipedia.org/wiki/%E7%BA ...

  9. 【转】Java HashMap工作原理(好文章)

    大部分Java开发者都在使用Map,特别是HashMap.HashMap是一种简单但强大的方式去存储和获取数据.但有多少开发者知道HashMap内部如何工作呢?几天前,我阅读了java.util.Ha ...

随机推荐

  1. Dom选择器--内容文本操作

    一.文本内容操作 内容: <body> <div id="i1"> 学习是我快乐? <a>晚饭吃什么</a> </div> ...

  2. centos7+mysql5.7.11实现主从复制

    1  首先检测当前的系统是否已经安装了MySQL yum list installed | grep mysql 如果有的话,删除 2  下载rpm库资源,在网页 https://dev.mysql. ...

  3. Hadoop生态圈-Kafka的完全分布式部署

    Hadoop生态圈-Kafka的完全分布式部署 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本篇博客主要内容就是搭建Kafka完全分布式,它是在kafka本地模式(https:/ ...

  4. MySQL的DML常用语法格式

    MySQL的DML常用语法格式 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 我们知道MySQL的查询大致分为单表查询,多表查询以及联合查询.多表查询,顾名思义,就是查询的结果可能 ...

  5. DataGridView更新数据到数据库

    WinFrom程序绑定了一个DataGridView控件,我需要添加一个button按钮来更改状态,还需要把更新之后的状态更新到数据库,如下图所示的这样: 首先先来拖控件,把界面做出来,自己拖一个Da ...

  6. CSS规范 - 优化方案--(来自网易)

    值缩写 缩写值可以减少CSS文件大小,并增加可读性和可维护性. 但并非所有的值都必须缩写,因为当一个属性的值缩写时,总是会将所有项都设置一遍,而有时候我们不希望设置值里的某些项. /* 比如我们用下面 ...

  7. 将网页设置为允许 XMLHttpRequest 跨域访问

    在非IE下,使用XMLHttpRequest 不能跨域访问, 除非要访问的网页设置为允许跨域访问. 将网页设置为允许跨域访问的方法如下: Java Response.AddHeader("A ...

  8. springMVC非注解常用的"处理器映射器"、"适配器"、"处理器"

    非注解处理器映射器1. org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping url 到bean name的映射2. or ...

  9. MVC控制器使用总结

    一.新手入门 1.特性 [AuthorizeFilter]  用于权限过滤 [HttpGet] [HttpPost] 2.参数 GET获取 [HttpGet] ) { return Json(&quo ...

  10. elasticsearch常用配置

    允许外网连接network.host,http.port,network.publish_host,network.bind_host别的机器或者网卡才能访问,否则只能是127.0.0.1或者loca ...