JDK1.8_HashMap源码__构造函数
HashMap的底层实现是一个Node类型的数组,也就是说使用put(key, value)方法的时候就把key和value根据hashcode值存在table数组相应的下标中,源码如下:
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
Node是一个实现了Map.Entry的泛型类,源码如下:
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
} public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; } public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
} public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
} public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
2 HashMap的构造函数
HashMap共有四个构造函数:
public HashMap()
public HashMap(int initialCapacity)
public HashMap(int initialCapacity, float loadFactor)
public HashMap(Map<? extends K, ? extends V> m)
2.1 无参构造函数:public HashMap()
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
官方在HashMap()构造方法的注释里说到:“使用默认初始化容量(16)和默认加载因子(0.75)来构造一个空的HashMap”,但是源码中只初始化了loadFactor而没有初始化默认容量,那么容量的初始化在哪里呢?
既然容量在构造函数里没有进行初始化,那么在使用put方法往hashMap中添加元素会发生怎么情况呢?
下面就让我们来put方法中一探究竟吧!
public V put(K key, V value) {
//这里调用了下面的putVal方法
return putVal(hash(key), key, value, false, true);
} final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K, V>[] tab;
Node<K, V> p;
int n, i;
//如果table为空或者长度为0,先进行扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; //省略剩余代码。。。。。。
}
为了不干扰这里先把暂时用不到的代码忽略掉,从源码中的黄色高亮代码中我们可以看到,在put的时候当table为null或者长度为0时将会调用它resize方法进行一次扩容,resize方法中发生了什么呢?
final Node<K, V>[] resize() {
//1. 这里的table由于没有初始化所以依然为null
Node<K, V>[] oldTab = table;
//2. 此时oldCap的值为0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//3. 获取旧的扩容阈值,此时threshold还没有赋值,因此它的值为默认值0
int oldThr = threshold;
int newCap, newThr = 0;
//如果旧的table中有元素,显然这里不符合条件
if (oldCap > 0) {
//忽略无关代码
} else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
//5. threshold变成了table数组长度与加载因子的积
float ft = (float) newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
threshold = newThr; @SuppressWarnings({"rawtypes", "unchecked"}) Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
table = newTab; //忽略无关代码 return newTab;
}
注意:这里出现了一个变量threshold,threshold是用来判断table是否需要扩容的阈值,当table中的元素数量大于threshold时table就会进行扩容。
从黄色高亮的代码中可以清楚的看到,resize方法以DEFAULT_INITIAL_CAPACITY为数组大小创建了一个新的数组替换旧的数组。
并将扩容阈值设置为DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY 也就是16*0.75 = 12
2.2 指定初始容量的构造函数:public HashMap(int initialCapacity)
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
源码中可以看到该构造函数调用了指定初始容量和加载因子的构造函数并将加载因子指定为默认值(0.75)。
2.3 指定初始容量和加载因子的构造函数:public HashMap(int initialCapacity, float loadFactor)
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
源码中首先对initialCapacity和loadFactor进行了验证,之后设置了扩容阈值threshold的值,(请记住这个变量,待会初始化table数组时还会用到它)
tableSizeFor方法是如何设置threshold的值的呢?这个方法的详细解释在另一篇随笔里:HashMap源码__tableSizeFor方法解析
现在我们只需要知道这个方法会返回一个不小于参数cap的最小的2的整数次幂的数,比如cap为10,就返回16、cap为17,就返回32。
table数组的初始化
通过对以上构造函数源码的解析发现,用于储存元素的table数组都没有在构造函数里初始化,那么table数组什么时候进行初始化呢? 答案就时调用put方法的时候!前面结束无参构造方法时提到过,put方法调用了putVal方法,而putVal方法对table数组进行了扩容,因此下面直接来看resize方法:
final Node<K, V>[] resize() {
//1. 这里的table由于没有初始化所以依然为null
Node<K, V>[] oldTab = table;
//2. 此时oldCap的值为0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//3. 获取旧的扩容阈值,由于在构造函数里设置过threshol的值,所以这里threshol的值为不小指定容量的最小2的整数次幂
int oldThr = threshold;
int newCap, newThr = 0;
//如果旧的table中有元素,显然这里不符合条件
if (oldCap > 0) {
//忽略无关代码 //4. 重点来了, 由于oldThr已经赋值了,所以会执行方法体中的newCap=oldThr,这样一来table数组的容量就变成了前面提到的threshol的值
} else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
//5. threshold变成了table数组长度与加载因子的积
float ft = (float) newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
threshold = newThr; @SuppressWarnings({"rawtypes", "unchecked"}) Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
table = newTab; //忽略无关代码 return newTab;
}
JDK1.8_HashMap源码__构造函数的更多相关文章
- 【集合框架】JDK1.8源码分析HashSet && LinkedHashSet(八)
一.前言 分析完了List的两个主要类之后,我们来分析Set接口下的类,HashSet和LinkedHashSet,其实,在分析完HashMap与LinkedHashMap之后,再来分析HashSet ...
- 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)
一.前言 在完成Map下的并发集合后,现在来分析ArrayBlockingQueue,ArrayBlockingQueue可以用作一个阻塞型队列,支持多任务并发操作,有了之前看源码的积累,再看Arra ...
- 【集合框架】JDK1.8源码分析之HashMap(一) 转载
[集合框架]JDK1.8源码分析之HashMap(一) 一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化 ...
- 【集合框架】JDK1.8源码分析之ArrayList详解(一)
[集合框架]JDK1.8源码分析之ArrayList详解(一) 一. 从ArrayList字表面推测 ArrayList类的命名是由Array和List单词组合而成,Array的中文意思是数组,Lis ...
- 集合之TreeSet(含JDK1.8源码分析)
一.前言 前面分析了Set接口下的hashSet和linkedHashSet,下面接着来看treeSet,treeSet的底层实现是基于treeMap的. 四个关注点在treeSet上的答案 二.tr ...
- 集合之LinkedHashSet(含JDK1.8源码分析)
一.前言 上篇已经分析了Set接口下HashSet,我们发现其操作都是基于hashMap的,接下来看LinkedHashSet,其底层实现都是基于linkedHashMap的. 二.linkedHas ...
- 集合之HashSet(含JDK1.8源码分析)
一.前言 我们已经分析了List接口下的ArrayList和LinkedList,以及Map接口下的HashMap.LinkedHashMap.TreeMap,接下来看的是Set接口下HashSet和 ...
- 【1】【JUC】JDK1.8源码分析之ArrayBlockingQueue,LinkedBlockingQueue
概要: ArrayBlockingQueue的内部是通过一个可重入锁ReentrantLock和两个Condition条件对象来实现阻塞 注意这两个Condition即ReentrantLock的Co ...
- 【1】【JUC】JDK1.8源码分析之ReentrantLock
概要: ReentrantLock类内部总共存在Sync.NonfairSync.FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQ ...
随机推荐
- poj-1753题题解思路
今天天气很好! 首先题意是这样的:: 翻盖游戏是在一个长方形的4x4场上进行的,其16个方格中的每一个都放置了双面的棋子.每一块的一边是白色的,另一边是黑色的,每一块都是躺着的,要么是黑色的,要么是白 ...
- THUWC2020 自闭记
DAY 1 报道 领胸牌和-围巾-! 发现我和 \(ssf\) 小姐姐一个考场. 合影+开幕式 宾馆睡了一觉-睡上午觉真的舒服. 合影时在c位! 开幕式.比上次夏令营不知道好到哪里去了,讲话都挺有意思 ...
- [bzoj4011] [洛谷P3244] [HNOI2015] 落忆枫音
Description 「恒逸,你相信灵魂的存在吗?」 郭恒逸和姚枫茜漫步在枫音乡的街道上.望着漫天飞舞的红枫,枫茜突然问出 这样一个问题. 「相信吧.不然我们是什么,一团肉吗?要不是有灵魂--我们也 ...
- 【java基础】妙记进制转换
一.二进制与十进制 1.正整数十进制转二进制 口诀:除二取余,倒序排列 解释:将一个十进制数除以二,得到的商再除以二,依此类推直到商等于一或零时为止,倒取将除得的余数,即换算为二进制数的结果 例如把5 ...
- 创建dynamics CRM client-side (三) - Execution Context
Execution Context 在代码执行的时候定义了event context. 当form或者grid发生event时候传递了execution context. 可以在event hand ...
- 数据结构与算法 --- js描述队列
js描述队列 队列的特性是只能在队尾插入元素,在队首删除元素,先进先出: 队列被用在很多地方,比如提交操作系统执行的一系列进程,打印任务池,模拟现实中的排队: //队列类 function Queue ...
- PYTHON经典算法-完美平方
问题描述: 给定一个正整数n,找到若干个完全平方数(例如:1,4,9),使得 它们的和等于n,完全平方数的个数最少. 问题示例: 给出n=12,返回3,因为12=4+4+4:给出n=13,返回2,因为 ...
- pymysql连接提示format: a number is required, not str
最近想随手写一个简单的员工管理系统,第一次使用python连接数据库,在这个过程中就遇到了一些问题,遂记录 遇到问题习惯性百度一下,很多教程都不适合新手,有些还不知道是不是瞎写的,所以我觉得有必要自己 ...
- Struts(六)
JSON(JavaScript Object Notation) 1.一种轻量级的数据交换格式 2.通常用于在客户端和服务器之间传递数据 3.jQuery的所有参数都是以JSON格式 ...
- 办公环境下k8s网络互通方案
在 kubernetes 的网络模型中,基于官方默认的 CNI 网络插件 Flannel,这种 Overlay Network(覆盖网络)可以轻松的实现 pod 间网络的互通.当我们把基于 sprin ...