HashMap中傻傻分不清楚的那些概念

开发 开发工具
很多人在通过阅读源码的方式学习Java,这是个很好的方式。而JDK的源码自然是首选。在JDK的众多类中,我觉得HashMap及其相关的类是设计的比较好的。很多人读过HashMap的代码,不知道你们有没有和我一样,觉得HashMap中关于容量相关的参数定义的太多了,傻傻分不清楚。

很多人在通过阅读源码的方式学习Java,这是个很好的方式。而JDK的源码自然是***。在JDK的众多类中,我觉得HashMap及其相关的类是设计的比较好的。很多人读过HashMap的代码,不知道你们有没有和我一样,觉得HashMap中关于容量相关的参数定义的太多了,傻傻分不清楚。

[[230071]]

其实,这篇文章介绍的内容比较简单,只要认真的看看HashMap的原理还是可以理解的,单独写一篇文章的原因是因为我后面还有几篇关于HashMap源码分析的文章,这些概念不熟悉的话阅读后面的文章会很吃力。

HashMap中重要的成员变量

先来看一下,HashMap中都定义了哪些成员变量。

上面是一张HashMap中主要的成员变量的图,其中有一个是我们本文主要关注的: size、loadFactor、threshold、DEFAULT_LOAD_FACTOR和DEFAULT_INITIAL_CAPACITY。

我们先来简单解释一下这些参数的含义,然后再分析他们的作用。

HashMap类中有以下主要成员变量:

  • transient int size; 

记录了Map中KV对的个数

  • loadFactor 

装载印子,用来衡量HashMap满的程度。loadFactor的默认值为0.75f(static final float DEFAULT_LOAD_FACTOR = 0.75f;)。

  • int threshold; 

临界值,当实际KV个数超过threshold时,HashMap会将容量扩容,threshold=容量*加载因子

  • 除了以上这些重要成员变量外,HashMap中还有一个和他们紧密相关的概念:capacity 

容量,如果不指定,默认容量是16(static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;)

可能看完了你还是有点蒙,size和capacity之间有啥关系?为啥要定义这两个变量。loadFactor和threshold又是干啥的?

size 和 capacity

HashMap中的size和capacity之间的区别其实解释起来也挺简单的。我们知道,HashMap就像一个“桶”,那么capacity就是这个桶“当前”最多可以装多少元素,而size表示这个桶已经装了多少元素。来看下以下代码:

  1. Map<String, String> map = new HashMap<String, String>(); 
  2. map.put("hollis""hollischuang"); 
  3.  
  4. Class<?> mapType = map.getClass(); 
  5. Method capacity = mapType.getDeclaredMethod("capacity"); 
  6. capacity.setAccessible(true); 
  7. System.out.println("capacity : " + capacity.invoke(map)); 
  8.  
  9. Field size = mapType.getDeclaredField("size"); 
  10. size.setAccessible(true); 
  11. System.out.println("size : " + size.get(map)); 

我们定义了一个新的HashMap,并想其中put了一个元素,然后通过反射的方式打印capacity和size。输出结果为:capacity : 16、size : 1

默认情况下,一个HashMap的容量(capacity)是16,设计成16的好处我在《全网把Map中的hash()分析的最透彻的文章,别无二家。》中也简单介绍过,主要是可以使用按位与替代取模来提升hash的效率。

为什么我刚刚说capacity就是这个桶“当前”最多可以装多少元素呢?当前怎么理解呢。其实,HashMap是具有扩容机制的。在一个HashMap***次初始化的时候,默认情况下他的容量是16,当达到扩容条件的时候,就需要进行扩容了,会从16扩容成32。

我们知道,HashMap的重载的构造函数中,有一个是支持传入initialCapacity的,那么我们尝试着设置一下,看结果如何。

  1. Map<String, String> map = new HashMap<String, String>(1); 
  2. map.put("hahaha""hollischuang"); 
  3.  
  4. Class<?> mapType = map.getClass(); 
  5. Method capacity = mapType.getDeclaredMethod("capacity"); 
  6. capacity.setAccessible(true); 
  7. System.out.println("capacity : " + capacity.invoke(map)); 
  8.  
  9. Map<String, String> map = new HashMap<String, String>(7); 
  10. map.put("hahaha""hollischuang"); 
  11.  
  12. Class<?> mapType = map.getClass(); 
  13. Method capacity = mapType.getDeclaredMethod("capacity"); 
  14. capacity.setAccessible(true); 
  15. System.out.println("capacity : " + capacity.invoke(map)); 
  16.  
  17.  
  18. Map<String, String> map = new HashMap<String, String>(9); 
  19. map.put("hahaha""hollischuang"); 
  20.  
  21. Class<?> mapType = map.getClass(); 
  22. Method capacity = mapType.getDeclaredMethod("capacity"); 
  23. capacity.setAccessible(true); 
  24. System.out.println("capacity : " + capacity.invoke(map)); 

分别执行以上3段代码,分别输出:capacity : 2、capacity : 8、capacity : 16。

也就是说,默认情况下HashMap的容量是16,但是,如果用户通过构造函数指定了一个数字作为容量,那么Hash会选择大于该数字的***个2的幂作为容量。(1->2、7->8、9->16)

这里有一个小建议:在初始化HashMap的时候,应该尽量指定其大小。尤其是当你已知map中存放的元素个数时。(《阿里巴巴Java开发规约》)

loadFactor 和 threshold

前面我们提到过,HashMap有扩容机制,就是当达到扩容条件时会进行扩容,从16扩容到32、64、128...

那么,这个扩容条件指的是什么呢?

其实,HashMap的扩容条件就是当HashMap中的元素个数(size)超过临界值(threshold)时就会自动扩容。

在HashMap中,threshold = loadFactor * capacity。

loadFactor是装载因子,表示HashMap满的程度,默认值为0.75f,设置成0.75有一个好处,那就是0.75正好是3/4,而capacity又是2的幂。所以,两个数的乘积都是整数(capacity为2也同样)。

对于一个默认的HashMap来说,默认情况下,当其size大于12(16*0.75)时就会触发扩容。

验证代码如下:

  1. Map<String, String> map = new HashMap<>(); 
  2. map.put("hollis1""hollischuang"); 
  3. map.put("hollis2""hollischuang"); 
  4. map.put("hollis3""hollischuang"); 
  5. map.put("hollis4""hollischuang"); 
  6. map.put("hollis5""hollischuang"); 
  7. map.put("hollis6""hollischuang"); 
  8. map.put("hollis7""hollischuang"); 
  9. map.put("hollis8""hollischuang"); 
  10. map.put("hollis9""hollischuang"); 
  11. map.put("hollis10""hollischuang"); 
  12. map.put("hollis11""hollischuang"); 
  13. map.put("hollis12""hollischuang"); 
  14. Class<?> mapType = map.getClass(); 
  15.  
  16. Method capacity = mapType.getDeclaredMethod("capacity"); 
  17. capacity.setAccessible(true); 
  18. System.out.println("capacity : " + capacity.invoke(map)); 
  19.  
  20. Field size = mapType.getDeclaredField("size"); 
  21. size.setAccessible(true); 
  22. System.out.println("size : " + size.get(map)); 
  23.  
  24. Field threshold = mapType.getDeclaredField("threshold"); 
  25. threshold.setAccessible(true); 
  26. System.out.println("threshold : " + threshold.get(map)); 
  27.  
  28. Field loadFactor = mapType.getDeclaredField("loadFactor"); 
  29. loadFactor.setAccessible(true); 
  30. System.out.println("loadFactor : " + loadFactor.get(map)); 
  31.  
  32. map.put("hollis13""hollischuang"); 
  33. Method capacity = mapType.getDeclaredMethod("capacity"); 
  34. capacity.setAccessible(true); 
  35. System.out.println("capacity : " + capacity.invoke(map)); 
  36.  
  37. Field size = mapType.getDeclaredField("size"); 
  38. size.setAccessible(true); 
  39. System.out.println("size : " + size.get(map)); 
  40.  
  41. Field threshold = mapType.getDeclaredField("threshold"); 
  42. threshold.setAccessible(true); 
  43. System.out.println("threshold : " + threshold.get(map)); 
  44.  
  45. Field loadFactor = mapType.getDeclaredField("loadFactor"); 
  46. loadFactor.setAccessible(true); 
  47. System.out.println("loadFactor : " + loadFactor.get(map)); 

输出结果:

  1. capacity : 16 
  2. size : 12 
  3. threshold : 12 
  4. loadFactor : 0.75 
  5.  
  6. capacity : 32 
  7. size : 13 
  8. threshold : 24 
  9. loadFactor : 0.75 

当HashMap中的元素个数达到13的时候,capacity就从16扩容到32了。

HashMap中还提供了一个支持传入initialCapacity,loadFactor两个参数的方法,来初始化容量和装载因子。不过,一般不建议修改loadFactor的值。

总结

HashMap中size表示当前共有多少个KV对,capacity表示当前HashMap的容量是多少,默认值是16,每次扩容都是成倍的。loadFactor是装载因子,当Map中元素个数超过loadFactor* capacity的值时,会触发扩容。loadFactor* capacity可以用threshold表示。

【本文是51CTO专栏作者Hollis的原创文章,作者微信公众号Hollis(ID:hollischuang)】

戳这里,看该作者更多好文

责任编辑:武晓燕 来源: 51CTO专栏
相关推荐

2021-03-10 08:56:37

Zookeeper

2021-07-27 07:31:16

JavaArrayList数组

2022-05-15 21:52:04

typeTypeScriptinterface

2024-02-29 09:08:56

Encoding算法加密

2021-02-14 22:33:23

Java字符字段

2020-10-30 08:20:04

SD卡TF卡存储

2018-12-17 12:30:05

Kubernetes存储存储卷

2023-02-27 15:46:19

数据元元数据

2020-03-03 17:35:09

Full GCMinor

2023-09-03 21:18:07

Python编程语言

2021-11-09 06:01:35

前端JITAOT

2021-02-08 23:47:51

文件存储块存储对象存储

2016-11-04 12:51:46

Unix网络IO 模型

2022-02-25 09:14:33

类变量共享实例变量

2020-11-11 07:32:18

MySQL InnoDB 存储

2023-04-11 15:57:49

JavaScriptCSSHTML

2021-01-13 08:10:26

接口IEnumeratorIEnumerable

2019-11-21 14:22:12

WiFiWLAN区别

2021-12-29 07:34:40

Filter CSS backdrop-fi

2021-06-07 09:20:56

Javascript运算符开发
点赞
收藏

51CTO技术栈公众号