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源码__构造函数的更多相关文章

  1. 【集合框架】JDK1.8源码分析HashSet && LinkedHashSet(八)

    一.前言 分析完了List的两个主要类之后,我们来分析Set接口下的类,HashSet和LinkedHashSet,其实,在分析完HashMap与LinkedHashMap之后,再来分析HashSet ...

  2. 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)

    一.前言 在完成Map下的并发集合后,现在来分析ArrayBlockingQueue,ArrayBlockingQueue可以用作一个阻塞型队列,支持多任务并发操作,有了之前看源码的积累,再看Arra ...

  3. 【集合框架】JDK1.8源码分析之HashMap(一) 转载

    [集合框架]JDK1.8源码分析之HashMap(一)   一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化 ...

  4. 【集合框架】JDK1.8源码分析之ArrayList详解(一)

    [集合框架]JDK1.8源码分析之ArrayList详解(一) 一. 从ArrayList字表面推测 ArrayList类的命名是由Array和List单词组合而成,Array的中文意思是数组,Lis ...

  5. 集合之TreeSet(含JDK1.8源码分析)

    一.前言 前面分析了Set接口下的hashSet和linkedHashSet,下面接着来看treeSet,treeSet的底层实现是基于treeMap的. 四个关注点在treeSet上的答案 二.tr ...

  6. 集合之LinkedHashSet(含JDK1.8源码分析)

    一.前言 上篇已经分析了Set接口下HashSet,我们发现其操作都是基于hashMap的,接下来看LinkedHashSet,其底层实现都是基于linkedHashMap的. 二.linkedHas ...

  7. 集合之HashSet(含JDK1.8源码分析)

    一.前言 我们已经分析了List接口下的ArrayList和LinkedList,以及Map接口下的HashMap.LinkedHashMap.TreeMap,接下来看的是Set接口下HashSet和 ...

  8. 【1】【JUC】JDK1.8源码分析之ArrayBlockingQueue,LinkedBlockingQueue

    概要: ArrayBlockingQueue的内部是通过一个可重入锁ReentrantLock和两个Condition条件对象来实现阻塞 注意这两个Condition即ReentrantLock的Co ...

  9. 【1】【JUC】JDK1.8源码分析之ReentrantLock

    概要: ReentrantLock类内部总共存在Sync.NonfairSync.FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQ ...

随机推荐

  1. python3三元运算

    条件:简单的条件判断语句并且有返回值 作用:简化代码和装X 格式:为True执行的语句 if 判断条件 else 为False执行的语句 例子 def f(a, b): ""&qu ...

  2. python3复习

    一.基础11.运行python代码cmd->python 文件路径/文件名称2.解释器针对linux/uinux系统3.注释单行注释    #多行注释    三个单引号或三个双引号4.变量法律规 ...

  3. 4.JavaSE之标识符

    标识符:Java所有的组成部分都需要名字.类名.变量名以及方法名都被称为标识符. 关键字:abstract.assert.boolean.breake.public.static.class...

  4. 机器学习回顾篇(15):集成学习之GDBT

    .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...

  5. python类型-序列

    注:本文档主要是学习<Python核心编程(第二版)>时做的资料整理. 1.序列 序列的成员是有序排列的,并且可以通过下标偏移量访问到它的一个或者几个成员,包括字符串(普通字符串和Unic ...

  6. 玩转Django2.0---Django笔记建站基础九(一)(Auth认证系统)

    第九章 Auth认证系统 Django除了有强大的Admin管理系统之外,还提供了完善的用户管理系统.整个用户管理系统可分为三大部分:用户信息.用户权限和用户组,在数据库中分别对应数据表auth_us ...

  7. 图像矫正技术深入探讨(opencv)

    刚进入实验室导师就交给我一个任务,就是让我设计算法给图像进行矫正.哎呀,我不太会图像这块啊,不过还是接下来了,硬着头皮开干吧! 那什么是图像的矫正呢?举个例子就好明白了. 我的好朋友小明给我拍了这几张 ...

  8. doT 这个模板 是怎么实现的?(1)

  9. CSS-15-定位

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  10. c++中各类型数据占据的字节长度

    c++中各种类型数据类型占据字节长度 首先罗列一下C++中的数据类型都有哪些: 1.整形:int.long 2.字符型:char.wchar_t 3.布尔型:bool 4.浮点型:float.doub ...