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. CodeSign error: no provisioning profile at path '/Users/zhht-2015/Library/MobileDevice/Provisioning Profiles/79693141-f98b-4ac4-8bb4-476c9475f265.mobileprovision'

    解决方法: 1.关闭Xcode,找到项目中的**.xcodeproj文件,点击右键,show package contents(打开包内容). 2.打开后找到project.pbxproj文件,用文本 ...

  2. 01.flask pycharm开启debug模式

    代码照旧

  3. 12.instanceof和类型转换

    Instanceof: 判断一个对象是什么类型的~,可以判断两个类之间是否存在父子关系 package com.oop.demo07; public class Person { public voi ...

  4. 关于SpringDataJpa中测试出现StackOverflowError错误问题

    在使用SpringDataJpa进行多表查询时,使用导航查询,每次都出现 StackOverflowError错误, 经过查找资料,网上百度,终于找到原因, StackOverflowError 是栈 ...

  5. .net mvc Bundle 自己配置

    遇到了个坑 来和大家分享一下 1.一个空的mvc项目需要引用 System.Web.Optimization 2.然后nuget添加 microsoft ASP.NET WEB OPTIMIZATIO ...

  6. HashMap在JDK7和JDK8中的区别

    在[深入浅出集合Map]中,已讲述了HashMap在jdk7中实现,在此就不再细说了 JDK7中的HashMap 基于链表+数组实现,底层维护一个Entry数组 Entry<K,V>[]  ...

  7. 发现 一个业务管理系统 解决了 orm 和 前端框架 剩下的 是 业务逻辑 了 。 哈

    解决了 orm 和 前端框架 剩下的 是 业务逻辑 了 . 哈 还有 各种 aop 组件 呢 . 大家 high 来 准备 用 fluent data  和 mysql 写一个 wcf 的 接口呢. ...

  8. 安全性与收尾工作 运用过滤器进行授权 精通ASP-NET-MVC-5-弗瑞曼

  9. Bootstrap自带的那些常用插件

    1.Bootstrap自带的那些常用插件. 1.1模态框 模态框的HTML代码放置的位置 务必将模态框的HTML代码放在文档的最高层级内(也就是说,尽量作为 body 标签的直接子元素),以避免其他组 ...

  10. sublime3 docblocker 注释插件的配置

    sublime3 docblocker插件定制自己的注释,配置步骤 DocBlockr很好用,不仅仅可以自动生成注释,还可以手动编辑注释的格式. 安装方法:  Cmd+Shift+P -> In ...