HashMap 是我们经常使用的一种数据结构。工作中会经常用到,面试也会总提到这个数据结构,找工作的时候,”HashTable 和HashMap的区别“被问到过没有?

本文会从原理,JDK源码,项目使用多个角度来分析HashMap。

1.HashMap是什么

JDK文档中如是说”基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)不保证映射的顺序“

里面大致包含如下意思:

HashMap是Map的实现,因此它内部的元素都是K-V(键,值)组成的。

HashMap内部元素是无序的

2.jdk中如何实现一个HashMap

HashMap在java.util包下,我们平时使用的类,有大部分都是这个包或者其子包的类

JDK中实现类的定义

  1. public class HashMap<K,V>
  2. extends AbstractMap<K,V>
  3. implements Map<K,V>, Cloneable, Serializable

它实现了Map接口

通常我们这么使用HashMap

  1. Map<Integer,String> maps=new HashMap<Integer,String>();
  2. , "a");
  3. , "b");

上面代码新建了一个HashMap并且往里插入了两个数据,这里不接受基本数据类型来做K,V

如果你这么写的话,就会出问题了

  1. Map<int,double> maps=new HashMap<int,double>();

上面例子很简单可是你知道内部他们怎么实现的吗?

我们来看看HashMap的构造方法

  1. public HashMap() {
  2. this.loadFactor = DEFAULT_LOAD_FACTOR;
  3. threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
  4. table = new Entry[DEFAULT_INITIAL_CAPACITY];
  5. init();
  6. }

都知道HashMap是个变长的数据结构,看了上面的构造方法可能你并不会认为它有那么神了。

  1. DEFAULT_LOAD_FACTOR   //默认加载因子,如果不制定的话是0.75
  1. DEFAULT_INITIAL_CAPACITY //默认初始化容量,默认是16
  1. threshold //阈(yu)值 根据加载因子和初始化容量计算得出

因此我们知道了,如果我们调用无参数的构造方法的话,我们将得到一个16容量的数组

数组是定长的,如何用一个定长的数据来表示一个不定长的数据呢,答案就是找一个更长的

下面来看看put方法是怎么实现的

  1. public V put(K key, V value) {
  2. if (key == null) //键为空的情况,HashMap和HashTable的一个区别
  3. return putForNullKey(value);
  4. int hash = hash(key.hashCode()); //根据键的hashCode算出hash值
  5. int i = indexFor(hash, table.length); //根据hash值算出究竟该放入哪个数组下标中
  6. for (Entry<K,V> e = table[i]; e != null; e = e.next) {//整个for循环实现了如果存在K那么就替换V
  7. Object k;
  8. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  9. V oldValue = e.value;
  10. e.value = value;
  11. e.recordAccess(this);
  12. return oldValue;
  13. }
  14. }
  15. modCount++;//计数器
  16. addEntry(hash, key, value, i); //添加到数组中
  17. return null;
  18. }

区区十几行代码,通过我添加的注释看懂并不难,细心的话可能会发现这里并没有体现变长的概念,如果我数据大于之前的容量的怎么继续添加呀,答案就在addEntry方法中

  1. void addEntry(int hash, K key, V value, int bucketIndex) {
  2. Entry<K,V> e = table[bucketIndex];
  3. table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
  4. if (size++ >= threshold)
  5. * table.length);
  6. }

这里显示了如果当前size>threshold的话那么就会扩展当前的size的两倍,如何扩展?

  1. void resize(int newCapacity) {
  2. Entry[] oldTable = table;
  3. int oldCapacity = oldTable.length;
  4. if (oldCapacity == MAXIMUM_CAPACITY) {
  5. threshold = Integer.MAX_VALUE;
  6. return;
  7. }
  8. Entry[] newTable = new Entry[newCapacity];
  9. transfer(newTable);
  10. table = newTable;
  11. threshold = (int)(newCapacity * loadFactor);
  12. }

new 一个新的数组,将旧数据转移到新的数组中,并且重新计算阈值,如何转移数据?

  1. void transfer(Entry[] newTable) {
  2. Entry[] src = table;
  3. int newCapacity = newTable.length;
  4. ; j < src.length; j++) {
  5. Entry<K,V> e = src[j];
  6. if (e != null) {
  7. src[j] = null;
  8. do {
  9. Entry<K,V> next = e.next;
  10. int i = indexFor(e.hash, newCapacity);
  11. e.next = newTable[i];
  12. newTable[i] = e;
  13. e = next;
  14. } while (e != null);
  15. }
  16. }
  17. }

根据hash值,和新的容量重新计算数据下标。天呀,太麻烦了吧。

到此为止我们知道了新建一个HashMap和添加一个HashMap之后源代码中都干了什么。

 3.hashcode你懂它不

HashMap是根据hashcode的来进行计算hash值的,equals方法默认也是通过hashcode来进行比较的

hashCode到底是个什么东西呢?

我们跟踪JDK源码到Object结果JDK确给了我们一个下面的本地方法

  1. public native int hashCode();

通过方法我们只能知道hashcode 是一个int值。

疑问更加多了,首先它如何保证不同对象的hashcode 值不一样呢,

既然hashcode是一个整形的,那么它最多的应该只能表示Integer.maxValue个值,

那么当大于这么多值的情况下这些对象的值又该如何表示呢。

要理解这些东西需要从操作系统说起了

//TODO 时间关系,后面再补

4.HashMap的优缺点

优点:超级快速的查询速度,如果有人问你什么数据结构可以达到O(1)的时间复杂度,没错是HashMap

动态的可变长存储数据(和数组相比较而言)

缺点:需要额外计算一次hash值

如果处理不当会占用额外的空间

 5.如果更加高效的使用HashMap

添加

前面我们知道了添加数据的时候,如果当前数据的个数加上1要大于hashmap的阈值的话,那么数组就会进行一个*2的操作。并且从新计算所有元素的在数组中的位置。

因此如果我们要添加一个1000个元素的hashMap,如果我们用默认值那么我么需要额外的计算多少次呢

当大于16*0.75=12的时候,需要从新计算 12次

当大于16*2*0.75=24的时候,需要额外计算 24次

……

当大于16*n*0.75=768的时候,需要额外计算 768次

所以我们总共在扩充过程中额外计算12+24+48+……+768次

因此强力建议我们在项目中如果知道范围的情况下,我们应该手动指定初始大小 像这样

  1. );

删除

JDK中如下方式进行删除

  1. public V remove(Object key) {
  2. Entry<K,V> e = removeEntryForKey(key);
  3. return (e == null ? null : e.value);
  4. }
  5. final Entry<K,V> removeEntryForKey(Object key) {
  6. : hash(key.hashCode());
  7. int i = indexFor(hash, table.length);
  8. Entry<K,V> prev = table[i];
  9. Entry<K,V> e = prev;
  10. while (e != null) {
  11. Entry<K,V> next = e.next;
  12. Object k;
  13. if (e.hash == hash &&
  14. ((k = e.key) == key || (key != null && key.equals(k)))) {
  15. modCount++;
  16. size--;
  17. if (prev == e)
  18. table[i] = next;
  19. else
  20. prev.next = next;
  21. e.recordRemoval(this);
  22. return e;
  23. }
  24. prev = e;
  25. e = next;
  26. }
  27. return e;
  28. }

根据上面代码我们知道了,如果删除是不进行了数组容量的重新定义的。所以,如果你有1000个元素的HashMap就算你最后删除只剩下一个了,你在内存中依然还有大于1000个容量,其中大于999个是空的。 为什么是大于因为扩容之后的HashMap实际容量大于1000个。

因此如果我们项目中有很大的HashMap,删除之后却很小了,我们还是弄一个新的小的存它 吧。

6.HashMap同步

如果HashMap在多线程下会出现什么问题呢

我们知道HashMap不是线程安全的(HashMap和HashTable的另外一个区别),如果我们也想要在多线程的环境下使用它怎么办呢?

也许你会说不是有HashTable吗?那我们就试试

  1. public class MyThread extends Thread { // 线程类
  2. private Map<Integer, String> maps; // 多线程处理的map
  3. public MyThread(Map<Integer, String> maps) {
  4. this.maps = maps;
  5. }
  6. @Override
  7. public void run() {
  8. );//随即删除的key
  9. op(delNumber);
  10. }
  11. public void op(int delNumber) {
  12. Iterator<Map.Entry<Integer, String>> t = maps.entrySet().iterator();
  13. while (t.hasNext()) {
  14. Map.Entry<Integer, String> entry = t.next();
  15. int key = entry.getKey();
  16. if (key == delNumber) { //看下key是否是需要删除的key是的话就删除
  17. maps.remove(key);
  18. break;
  19. }
  20. }
  21. }
  22. }
  1. public class HashMapTest {
  2. public static void main(String[] args) {
  3. testSync();
  4. }
  5. public static void testSync(){
  6. );
  7. //      Map<Integer, String> maps = new HashMap<Integer, String>(10000);
  8. //      Map<Integer, String> maps = new ConcurrentHashMap<Integer, String>(10000);
  9. ; i < 10000; i++) {
  10. maps.put(i, "a");
  11. }
  12. ;i<10;i++){
  13. new MyThread(maps).start();
  14. }
  15. }
  16. }

我们使用HashTable来运行试试,不一会就出现了如下错误信息

  1. Exception in thread "Thread-6" java.util.ConcurrentModificationException
  2. )
  3. )
  4. )
  5. Exception in thread "Thread-4" java.util.ConcurrentModificationException
  6. )
  7. )
  8. )
  9. Exception in thread "Thread-2" java.util.ConcurrentModificationException
  10. )
  11. )
  12. )
  13. Exception in thread "Thread-1" java.util.ConcurrentModificationException
  14. )
  15. )
  16. )
  17. Exception in thread "Thread-8" java.util.ConcurrentModificationException
  18. )
  19. )
  20. )
  21. Exception in thread "Thread-9" java.util.ConcurrentModificationException
  22. )
  23. )
  24. )
  25. Exception in thread "Thread-5" java.util.ConcurrentModificationException
  26. )
  27. )
  28. )
  29. ):  [../../../src/share/back/util.c:820]

不是说是安全的不?为什么会出现这个问题呢,继续看源代码

  1. public T next() {
  2. if (modCount != expectedModCount)
  3. throw new ConcurrentModificationException();
  4. return nextElement();
  5. }

当修改之后的计数器和期望的不一致的时候就会抛出异常了。对应于上面代码,线程1,遍历的时候假如有100个,本来删除之后就99个,但是线程2这段时间也删除了一个

所以实际只有98个了,线程1并不知道,当线程1调用next方法时候比较下结果不对,完了,数据不对了,老板要扣工资了,线程自己也解决不了,抛出去吧,别引起更大的问题了。

于是你得到了一个ConcurrentModificationException。

所以以后要注意了,HashTable,vector都不是绝对线程安全的了,所以我们需要将maps加上同步

  1. public void op(int delNumber) {
  2. synchronized (maps) {
  3. Iterator<Map.Entry<Integer, String>> t = maps.entrySet().iterator();
  4. while (t.hasNext()) {
  5. Map.Entry<Integer, String> entry = t.next();
  6. int key = entry.getKey();
  7. if (key == delNumber) { // 看下key是否是需要删除的key是的话就删除
  8. maps.remove(key);
  9. break;
  10. }
  11. }
  12. }
  13. }

synchronized(maps)加上之后就不会出现问题了,就算你用的是HashMap都不会出问题。

其实JDK中在早在1.5之后又了ConcurrentHashMap了这个类你可以放心的在多线程下使用并且不需要加任何同步 了。

java.util.HashMap 解析的更多相关文章

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

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

  2. java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String

    问题背景:从前端传来的json字符串中取某些值,拼接成json格式入参调外部接口. 报如下错: java.lang.ClassCastException: java.util.HashMap cann ...

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

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

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

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

  5. LinkedHashMap和HashMap的比较使用 由于现在项目中用到了LinkedHashMap,并不是太熟悉就到网上搜了一下。 ? import java.util.HashMap; impo

    LinkedHashMap和HashMap的比较使用 由于现在项目中用到了LinkedHashMap,并不是太熟悉就到网上搜了一下. import java.util.HashMap; import ...

  6. java.util.HashMap和java.util.HashTable (JDK1.8)

    一.java.util.HashMap 1.1 java.util.HashMap 综述 java.util.HashMap继承结构如下图 HashMap是非线程安全的,key和value都支持nul ...

  7. 关于spring mybateis 定义resultType="java.util.HashMap"

    关于spring mybateis  定义resultType="java.util.HashMap" List<HashMap<String, Object>& ...

  8. JDK1.8源码(七)——java.util.HashMap 类

    本篇博客我们来介绍在 JDK1.8 中 HashMap 的源码实现,这也是最常用的一个集合.但是在介绍 HashMap 之前,我们先介绍什么是 Hash表. 1.哈希表 Hash表也称为散列表,也有直 ...

  9. org.apache.ibatis.builder.IncompleteElementException: Could not find result map java.util.HashMap

    这样的配置有问题吗? <select id="getFreightCollectManagementList" resultMap="java.util.HashM ...

随机推荐

  1. 原来Notepad++也有列模式(转)

    引子 一直在用Notepad++,小巧.顺手.偶尔使用UltraEdit来处理列模式:UE越来越大,启动时间太长,早都烦了.今天上网,偶然间看到,Notepad++也有列模式.拜拜UE,彻底删除你. ...

  2. HTTP(HyperText Transport Protocol)超文本传输协议的状态码

    关于HTTP状态码:是用于表示网页服务器HTTP响应状态的3位数字代码. 所有状态码的第一个数字代表了响应的五种状态之一. 1xx:消息:这一类型的状态码代表请求已被接受,需要继续处理 2xx:成功: ...

  3. 第K短路模板【POJ2449 / 洛谷2483 / BZOJ1975 / HDU6181】

    1.到底如何求k短路的? 我们考虑,要求k短路,要先求出最短路/次短路/第三短路……/第(k-1)短路,然后访问到第k短路. 接下来的方法就是如此操作的. 2.f(x)的意义? 我们得到的f(x)更小 ...

  4. 封装boto3 api用于服务器端与AWS S3交互

    由于使用AWS的时候,需要S3来存储重要的数据. 使用Python的boto3时候,很多重要的参数设置有点繁琐. 重新写了一个类来封装操作S3的api.分享一下: https://github.com ...

  5. 50 years, 50 colors HDU - 1498(最小点覆盖或者说最小顶点匹配)

    On Octorber 21st, HDU 50-year-celebration, 50-color balloons floating around the campus, it's so nic ...

  6. 全网第二好懂的FFT(快速傅里叶变换)

    声明:本FFT是针对OI的.专业人员请出门左拐. Ⅰ前言 很久以前,我打算学习FFT. 然而,算法导论讲的很详细,却看不懂.网上博客更别说了,什么频率之类的都来了.我暗自下了决心:写一篇人看得懂的FF ...

  7. 【动态规划】【滚动数组】Educational Codeforces Round 26 D. Round Subset

    给你n个数,让你任选K个,使得它们乘起来以后结尾的0最多. 将每个数的因子2和因子5的数量求出来,记作a[i]和b[i]. 答案就是max{ min{Σa[i],Σb[i]} }(a[i],b[i]是 ...

  8. 【树形dp】Codeforces Round #405 (rated, Div. 1, based on VK Cup 2017 Round 1) B. Bear and Tree Jumps

    我们要统计的答案是sigma([L/K]),L为路径的长度,中括号表示上取整. [L/K]化简一下就是(L+f(L,K))/K,f(L,K)表示长度为L的路径要想达到K的整数倍,还要加上多少. 于是, ...

  9. 20162303实验三 敏捷开发与XP实践-1

    北京电子科技学院(BESTI) 实 验 报 告 课程:程序设计与数据结构 班级: 1623 姓名: 石亚鑫 学号:20162303 成绩: 2分 指导教师:娄嘉鹏 王志强 实验日期:5月12日 实验密 ...

  10. Hibernate的QBC检索方式

    Hibernate的QBC检索方式 一直习惯了Hibernate的HQL查询,一直也觉得挺方便,对于最近项目里出现的QBC(org.hibernate.Criteria接口)也是报着一种看看的心理,因 ...