注:玩的是JDK1.7版本

一:还是原来的风格,先上一下类的继承关系图,这样能够比较清楚的知道此类的相关特性

二:HashMap.java 的代码比较难看,所以,我看了几天,写的话也分开来写,这样能表达的更清晰,HashMap.java 的底层数据结构,本质是单向链表数组,如下所示是单向链中节点的结构信息

三:既然 HashMap.java 的底层数据结构是单向链表数组,那么我们便可以想象一下数组和单向链表这两种数据结构的特点,然后再回头想想 HashMap.java 的实现,然后再看源码就更容易理解了,如下所示是可能的结构样子。

1)通常应该是如下所示的结构形式,哈希值比较均匀,部分存在冲突

2)极端情况可能是如下所示的结构形式,存在大量冲突,单向链表数组 变成了 单向链表

3)极端情况可能是如下所示的结构形式,没有任何冲突,单向链表数组 变成了 简单的数组

四:看完如上 HashMap.java 的底层数据结构的可能呈现的样子之后,我们再看一下 HashMap.java 中的有关属性,个人感觉可能的结构了解后,更容易理解这些属性的本质,注意:HashMap.java 的特点是可动态扩容哈!

1)HashMap的默认初始化容量(16),表示HashMap当前最多能够装载16个元素,注意:必须是2的幂次方

    /**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

2)HashMap的最大容量 2的30次方=1073741824

    /**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;

3)HashMap默认的装载因子(0.75f),用于衡量HashMap满的程度,0.75=3/4,换言之当HashMap中的元素超过当前容量的3/4的时候,HashMap就会进行动态的扩容

    /**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;

4)HashMap没有真正放置元素时,是一个空数组

    /**
* An empty table instance to share when the table is not inflated.
*/
static final Entry<?,?>[] EMPTY_TABLE = {};

5)HashMap没有真正放置元素时,是一个空数组,注意:HashMap的容量长度必须是2的幂次方

    /**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

6)HashMap中 key-value mapping 映射对的个数

    /**
* The number of key-value mappings contained in this map.
*/
transient int size;

7)HashMap 动态扩容的临界值,每当 size>threshold 的时候,HashMap 就会动态扩容了,threshold = capacity * load factor

    /**
* The next size value at which to resize (capacity * load factor).
* @serial
*/
// If table == EMPTY_TABLE then this is the initial capacity at which the
// table will be created when inflated.
int threshold;

8)装载因子,用于 HashMap 是否进行动态扩容计算的变量之一,默认值是0.75f,如无必要通常不必改变

    /**
* The load factor for the hash table.
*
* @serial
*/
final float loadFactor;

五:实验实验,玩一把,看看什么情况

1)结论性信息的都放在了代码注释之中,如下所示(可以自己动手调整一下参数配置,跑跑看)

/**
* @description:玩一把HashMap
* @author:godtrue
* @create:2018-09-28
*/
public class TestMap { /**
* 开始循环的基数
*/
public static final int START_LOOP=1; /**
* 结束循环的基数
*/
public static final int END_LOOP=17; /**
*
*@description: 测试入口,主方法
*@param args
*@return: void
*@author: godtrue
*@createTime: 2018-09-28 9:53
*@version: v1.0
*/
public static void main(String[] args) {
/**
* 此处可以调用不同的构造方法来观察,HashMap 的容量、装载因子、扩容临界值、K-V映射对的个数等重点参数之间的关系
* 重点强调一次
* 1:capacity 容量——HashMap最多能装载元素个数
* 2:loadFactor 装载因子——表示HashMap满的程度,判断HashMap是否扩容的变量之一
* 3:threshold 扩容临界值——判断HashMap是否扩容的标准( threshold = capacity * loadFactor )
* 4:size HashMap 已经装载的元素个数——已经转载进入HashMap的 K-V mapping 映射对的个数
*
* 5:HashMap 能够动态扩容,当 size > threshold 时 HashMap,便会自动库容,每次扩容的长度是原来 容量 的 2 倍
* 6:HashMap 的 key 和 value 都可以为 null
* 7:HashMap 是非线程安全的
*
*/
//Map hashMap = new HashMap<String,String>();
Map hashMap = new HashMap<String,String>(1);
for(int i = TestMap.START_LOOP;i<TestMap.END_LOOP;i++){
/**
* 此处可以控制 key 值,来观察一下运行的情况
*/
//hashMap.put(null,"i am godtrue"+i);
//hashMap.put("godtrue","i am godtrue"+i);
hashMap.put("godtrue"+i,"i am godtrue"+i);
printMapInfo(hashMap,i);
}
System.out.println("hashMap is : "+hashMap);
} /**
*
*@description: 将 Map 的参数信息打印到控制台,主要是打印 容量、装载因子、扩容临界值、K-V映射对的个数 等参数信息
*@param map
*@param i
*@return: void
*@author: godtrue
*@createTime: 2018-09-28
*@version: v1.0
*/
private synchronized static void printMapInfo(Map map,int i){
System.out.println("添加第 "+i +" 个元素后");
printMapMethodInfo(map,"capacity");
printMapFieldInfo(map,"loadFactor");
printMapFieldInfo(map,"threshold");
printMapFieldInfo(map,"size");
System.out.println("***********************************************\n");
} /**
*
*@description: 将 Map 的属性信息打印到控制台,主要是打印 装载因子、扩容临界值、K-V映射对的个数 等参数信息
*@param map
*@param property
*@return: void
*@author: godtrue
*@createTime: 2018-09-28
*@version: v1.0
*/
private static void printMapFieldInfo(Map map,String property){
try {
Class<?> mapType = map.getClass();
Field field = mapType.getDeclaredField(property);
field.setAccessible(true);
System.out.println(field +" : "+ field.get(map));
}catch (Exception e){
System.err.println("e is :"+e);
e.printStackTrace();
}
} /**
*
*@description: 将 Map 的方法信息打印到控制台,主要是想打印 容量 的信息
*@param map
*@param property
*@return: void
*@author: godtrue
*@createTime: 2018-09-28
*@version: v1.0
*/
private static void printMapMethodInfo(Map map,String property){
try {
Class<?> mapType = map.getClass();
Method method = mapType.getDeclaredMethod(property);
method.setAccessible(true);
System.out.println(method +" : "+ method.invoke(map));
}catch (Exception e){
System.err.println("e is :"+e);
e.printStackTrace();
}
}
}

2)仔细观察如下日志,可以印证上述代码注释中的部分结论,注意:请重点关注 capacity、loadFactor、threahold、size之间的变化关系

添加第 1 个元素后
int java.util.HashMap.capacity() : 1
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 0
transient int java.util.HashMap.size : 1
*********************************************** 添加第 2 个元素后
int java.util.HashMap.capacity() : 2 //扩容
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 1
transient int java.util.HashMap.size : 2
*********************************************** 添加第 3 个元素后
int java.util.HashMap.capacity() : 4 //扩容
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 3
transient int java.util.HashMap.size : 3
*********************************************** 添加第 4 个元素后
int java.util.HashMap.capacity() : 4
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 3
transient int java.util.HashMap.size : 4
*********************************************** 添加第 5 个元素后
int java.util.HashMap.capacity() : 8 //扩容
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 6
transient int java.util.HashMap.size : 5
*********************************************** 添加第 6 个元素后
int java.util.HashMap.capacity() : 8
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 6
transient int java.util.HashMap.size : 6
*********************************************** 添加第 7 个元素后
int java.util.HashMap.capacity() : 8
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 6
transient int java.util.HashMap.size : 7
*********************************************** 添加第 8 个元素后
int java.util.HashMap.capacity() : 8
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 6
transient int java.util.HashMap.size : 8
*********************************************** 添加第 9 个元素后
int java.util.HashMap.capacity() : 16 //扩容
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 12
transient int java.util.HashMap.size : 9
*********************************************** 添加第 10 个元素后
int java.util.HashMap.capacity() : 16
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 12
transient int java.util.HashMap.size : 10
*********************************************** 添加第 11 个元素后
int java.util.HashMap.capacity() : 16
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 12
transient int java.util.HashMap.size : 11
*********************************************** 添加第 12 个元素后
int java.util.HashMap.capacity() : 16
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 12
transient int java.util.HashMap.size : 12
*********************************************** 添加第 13 个元素后
int java.util.HashMap.capacity() : 32 //扩容
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 24
transient int java.util.HashMap.size : 13
*********************************************** 添加第 14 个元素后
int java.util.HashMap.capacity() : 32
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 24
transient int java.util.HashMap.size : 14
*********************************************** 添加第 15 个元素后
int java.util.HashMap.capacity() : 32
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 24
transient int java.util.HashMap.size : 15
*********************************************** 添加第 16 个元素后
int java.util.HashMap.capacity() : 32
final float java.util.HashMap.loadFactor : 0.75
int java.util.HashMap.threshold : 24
transient int java.util.HashMap.size : 16
*********************************************** hashMap is : {godtrue4=i am godtrue4, godtrue5=i am godtrue5, godtrue2=i am godtrue2, godtrue3=i am godtrue3, godtrue8=i am godtrue8, godtrue9=i am godtrue9, godtrue6=i am godtrue6, godtrue7=i am godtrue7, godtrue1=i am godtrue1, godtrue10=i am godtrue10, godtrue12=i am godtrue12, godtrue11=i am godtrue11, godtrue14=i am godtrue14, godtrue13=i am godtrue13, godtrue16=i am godtrue16, godtrue15=i am godtrue15} Process finished with exit code 0

六:几个为什么?

1)为什么 HashMap 的默认容量是 16, 并且强调容量必须是 2 的幂次方呢?

HashMap 的容量必须是 2 的幂次方,主要是出于性能的考虑,可以使用 位于运算 来计算单向链表数组的下标位置。

详情可参考

http://www.hollischuang.com/archives/2091

http://www.cnblogs.com/chenssy/p/3521565.html

https://blog.csdn.net/justloveyou_/article/details/62893086

默认值为什么是16呢?

首先,16 是 2的4次方,符合容量是 2 的幂次方的强性规定,其次,我猜测 16 可能是一个样本比较集中的 HashMap的容量

2)为什么 HashMap 的默认装载因子是 0.75f,并且不建议自定义呢?

HashMap 的默认装载因子是0.75f,主要是时间和空间成本上一种折衷。

详情可参考

http://alex09.iteye.com/blog/539545/

3)为什么 HashMap 在扩容的时候,总是扩大原来容量的 2 倍呢?

首先,容量扩大 2 倍后,仍然符合容量是 2 的幂次方的强性规定(注意:容量是2的幂次方),其次,同样是出于性能考虑,直接通过左移移位便可实现

七)本篇,编写的过程中参看了 http://www.hollischuang.com/archives/2416

分析轮子(十)- HashMap.java 之概念梳理的更多相关文章

  1. java入门概念梳理总结

    Java入门学习 简介 public class HelloWorld { public static void main(String []args) { System.out.println(&q ...

  2. 分析轮子(五)- Vector.java

    注:玩的是JDK1.7版本 一: 先上类图,从类图上看和 ArrayList.java 非常相像,可查看 分析轮子(一)-ArrayList.java 二:然后看源码,发现和 ArrayList.ja ...

  3. 分析轮子(四)- 我也玩一把 Serializable.java

    前言:在写 分析轮子(一)-ArrayList.java 的时候曾经下过一个结论 “实现Serializable接口,表示ArrayList是可序列化的”,这个结论是以往学习的经验所得,并且平时在编程 ...

  4. Java集合源码分析(十)——TreeSet

    简介 TreeSet就是一个集合,里面不能有重复的元素,但是元素是有序的. TreeSet其实就是调用了TreeMap实现的,所以,它也不是线程安全的.可以实现自然排序或者根据传入的Comparato ...

  5. Java进阶(三十九)Java集合类的排序,查找,替换操作

    Java进阶(三十九)Java集合类的排序,查找,替换操作 前言 在Java方向校招过程中,经常会遇到将输入转换为数组的情况,而我们通常使用ArrayList来表示动态数组.获取到ArrayList对 ...

  6. Java进阶(三十五)java int与integer的区别

    Java进阶(三十五)java int与Integer的区别 前言 int与Integer的区别从大的方面来说就是基本数据类型与其包装类的区别: int 是基本类型,直接存数值,而Integer是对象 ...

  7. Java进阶(二十五)Java连接mysql数据库(底层实现)

    Java进阶(二十五)Java连接mysql数据库(底层实现) 前言 很长时间没有系统的使用java做项目了.现在需要使用java完成一个实验,其中涉及到java连接数据库.让自己来写,记忆中已无从搜 ...

  8. 分析轮子(二)- << ,>>,>> (左移、右移、无符号右移)

    前言:写 分析轮子(一)-ArrayList.java 的时候看到源码中有 int newCapacity = oldCapacity + (oldCapacity >> 1); 这样的代 ...

  9. Java 系统学习梳理_【All】

    Java基础 1. Java学习---JDK的安装和配置 2. Java学习---Java代码编写规范 2. Java学习---HashMap和HashSet的内部工作机制 3. Java学习---J ...

随机推荐

  1. Java大数相加-hdu1047

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1047 题目描述: 题意有点绕,但是仔细的读了后就发现是处理大数相加的问题.注意:输入数据有多组,每组输 ...

  2. Best Cow Fences POJ - 2018 (二分)

    Farmer John's farm consists of a long row of N (1 <= N <= 100,000)fields. Each field contains ...

  3. 浪里个浪 FZU - 2261

    TonyY是一个喜欢到处浪的男人,他的梦想是带着兰兰姐姐浪遍天朝的各个角落,不过在此之前,他需要做好规划. 现在他的手上有一份天朝地图,上面有n个城市,m条交通路径,每条交通路径都是单行道.他已经预先 ...

  4. Java 实现String语句的执行(Jexl)

    https://www.jianshu.com/p/1000719e49fa 1.maven 导入库 <dependency> <groupId>org.apache.comm ...

  5. BZOJ.4199.[NOI2015]品酒大会(后缀数组 单调栈)

    BZOJ 洛谷 后缀自动机做法. 洛谷上SAM比SA慢...BZOJ SAM却能快近一倍... 显然只需要考虑极长的相同子串的贡献,然后求后缀和/后缀\(\max\)就可以了. 对于相同子串,我们能想 ...

  6. 洛谷P1541 乌龟棋(四维DP)

    To 洛谷.1541 乌龟棋 题目背景 小明过生日的时候,爸爸送给他一副乌龟棋当作礼物. 题目描述 乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数).棋盘第1格是唯一的起点,第N格是终点,游 ...

  7. Hass.io: add-on Samba

    { "workgroup": "WORKGROUP", "name": "hassio", "guest&qu ...

  8. Java 多线程 高可用原则

    高可用原则 1 降级 降级开关的设计思路如下: 1. 集中管理开关:把开关推送到各个应用. 2. 可降级的多级读服务:比如服务调用降级为只读本地缓存.只读分布式缓存.只读默认降级数据(如库存状态默认有 ...

  9. Node爬取简书首页文章

    Node爬取简书首页文章 博主刚学node,打算写个爬虫练练手,这次的爬虫目标是简书的首页文章 流程分析 使用superagent发送http请求到服务端,获取HTML文本 用cheerio解析获得的 ...

  10. c# 后台 添加datable 数据

    public void AddRows(HtmlTable table(表名),DataTable dt(数据源), string i(自增长))         {                  ...