public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
基于hash表的Map集合,允许key=null和value=null,HashMap是不同步的,不能保证Map集合的顺序,它是无序的Map集合.
HashMap有两个参数影响它的性能: 初始化容量(initial capacity) 和 负载因子(load factor)。
初始化容量:就是创建 hash表时的容量
负载因子:负载因子是衡量在哈希表的容量被自动增加之前,哈希表被允许获得多少满的度量。
当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表将被重新哈希(即重新构建内部数据结构),
这样哈希表的桶数大约是桶数的两倍。
默认的负载因子是0.75f,这是一个在时间和空间上的一个折中;较高的值减少了空间开销,但增加了查找成本(主要表现在HaspMap的get和put操作)。
   如果初始容量大于最大条目数除以负载因子,则不会发生任何重哈希操作。
底层数据结构是链表数组,
 /**
* 默认初始化容量是16
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /**
* 最大的容量,如果较高的值由带参数的任何构造函数隐式指定,则使用。
* 必须是2的幂,最大容量为1073741824
*/
static final int MAXIMUM_CAPACITY = 1 << 30;// /**
* 当构造函数中没有指定时使用的负载因子,默认是0.75f;
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 一个桶的树化阈值
// 当桶中元素个数超过这个值时,需要使用红黑树节点替换链表节点
// 这个值必须为 8,要不然频繁转换效率也不高
static final int TREEIFY_THRESHOLD = 8; // 一个树的链表还原阈值
// 当扩容时,桶中元素个数小于这个值,就会把树形的桶元素 还原(切分)为链表结构
// 这个值应该比上面那个小,至少为 6,避免频繁转换
static final int UNTREEIFY_THRESHOLD = 6; // 哈希表的最小树形化容量
// 当哈希表中的容量大于这个值时,表中的桶才能进行树形化
// 否则桶内元素太多时会扩容,而不是树形化
// 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
static final int MIN_TREEIFY_CAPACITY = 64;

基本的hash表节点:

     /**
* 基本的hash表节点,
* (参见下面的forTreeNode子类,以及LinkedHashMap中的条目子类。)
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//hash值 final修饰的
final K key;//键 final修饰的
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;
}
}
 /**
* 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.)
* 第一次初始化的时候使用这个表。当分配时,长度总是2的幂
*/
transient Node<K,V>[] table; /**
* Holds cached entrySet(). Note that AbstractMap fields are used for keySet() and values().
* 保存缓存entrySet()。注意AbstractMap字段用于keySet()和values()。
*/
transient Set<Map.Entry<K,V>> entrySet; /**
* The number of key-value mappings contained in this map.
* 此映射中包含的键值映射的数目。
*/
transient int size; /**
* 被修改的次数
*/
transient int modCount; /**
* The next size value at which to resize (capacity * load factor).
* 要调整大小的下一个大小值(容量*负载因子)。
* @serial
*/
int threshold;//要调整大小的下一个大小值(容量*负载因子)。 /**
* 哈希表的负载因子。
* @serial
*/
final float loadFactor;

构造函数:

 /**
* 构造一个空的HashMap,使用专门的初始化容量和默认的负载因子。
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
} /**
* (16) and the default load factor (0.75).
* 构造一个空的HashMap,使用默认的初始化容量(16)和默认的负载因子(0.75f)。
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
} /**
* 构造一个新的HashMap,使用与指定映射相同的映射。装载因子使用默认的(0.75f),和足够容纳指定Map中的映射的初始容量。
* @param m the map whose mappings are to be placed in this map 要在此映射中放置其映射的映射的映射
* @throws NullPointerException if the specified map is null 如果这个指定的map为null,则抛出空指针异常NullPointerException
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
  /**
* 实现 Map.putAll和 Map的构造函数
* @param m the map
* @param evict 当最初构造这个map的时候为false,否则为true(传递到之后的插入节点的方法中)。
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY);
if (t > threshold) {
threshold = tableSizeFor(t);
}
}else if (s > threshold) {
resize();
}
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
 /**
* @return 返回此映射中键值映射的数目。
*/
public int size() {
return size;
} /**
* 判断该map是否为空
* @return 当此map中不含键值对的时候,返回true.
*/
public boolean isEmpty() {
return size == 0;
}

get方法(先比较hash,若相等在比较equals):

1. Key==null的时候,判断map也为空,就返回null
2. 根据node数组下标找到node数组下标
3. 如果当前node链表只存在一个数据就直接取value值
如果当前node链表存在多个node元素,则循环遍历node链表,分别对他们的hash值和key值,value值进行判断,知道找到node以后返回Value值,如果没有找到返回null。

  /**
* 根据键key获取值value,如果此map不包含这个key,则返回null。
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
*
* <p>A return value of {@code null} does not <i>necessarily</i>
* indicate that the map contains no mapping for the key; it's also
* possible that the map explicitly maps the key to {@code null}.
* The {@link #containsKey containsKey} operation may be used to
* distinguish these two cases.
*
* @see #put(Object, Object)
*/
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
} /**
* Implements Map.get and related methods
* Map.get相关方法的实现
* @param hash hash for key 键key的hash值
* @param key the key 键key
* @return the node, or null if none 返回根据key及key的hash找到的这个节点;如果这个节点为空,则返回null
*
*
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab;
Node<K,V> first, e;
int n;
K k;
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
// always check first node 总是会检查哈希表的第一个节点
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) {
return first;
}
if ((e = first.next) != null) {//如果第一个节点的下一个节点不为空
if (first instanceof TreeNode) {//如果该节点是TreeNode类型的(红黑树)
return ((TreeNode<K, V>) first).getTreeNode(hash, key);//调用红黑树的查找方法
}
do {
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
return e;
}
} while ((e = e.next) != null);
}
}
return null;
}

put方法:

1. 首先判断node数组,也就是数组是否为空,为空的话就初始化hashmap
2. 如果node数组不为空的话,就判断key是否为空,为空的话就将放到数组的第一个位置
3. 就通过key获取hash值,通过hash值做比较,如果key的hash值相等 并且key.equals(e.key)也相等的话,就将新的value替换掉旧的。如果条件不满足就创建一个node,且用上一个node的next指向新创建的node 。

 /**
* 往map中添加新的键值对,如果键key存在,则将该键key对应的旧值value替换为新值value
*
* @param key 要与指定值关联的键
* @param value 值与指定的键关联
* @return 与key关联的前一个值,如果没有key的映射,则为null。(null返回值还可以指示以前将null与key关联的映射。)
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
} /**
* Implements Map.put and related methods
* 实现Map.put和相关方法
* @param hash hash for key key的hash值
* @param key the key 键
* @param value the value to put 值
* @param onlyIfAbsent 如果是true,不能改变已存在的值
* @param evict 如果为false,该表处于创建模式
* @return 返回前一个值,如果没有,则为空
* 根据key算hash,根据容量和hash算index,table[index]没有直接添加到数组中,table[index]有,若index位置同一个key则更新,
* 否则遍历next是否有,有则更新,无则新增,最后根据thread与size判断是否扩容。注:扩容时容量翻倍,重新算hash复制到新数组
*
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab;
Node<K,V> p;
int n, i;
if ((tab = table) == null || (n = tab.length) == 0) {
n = (tab = resize()).length;
}
if ((p = tab[i = (n - 1) & hash]) == null) {
tab[i] = newNode(hash, key, value, null);
}else {
Node<K,V> e; K k;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) {
e = p;
}else if (p instanceof TreeNode) {
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
}
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) { // -1 for 1st
treeifyBin(tab, hash);
}
break;
}
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
break;
}
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null) {
e.value = value;
}
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold) {
resize();
}
afterNodeInsertion(evict);
return null;
}

JDK源码阅读--HashMap的更多相关文章

  1. JDK源码阅读(一):Object源码分析

    最近经过某大佬的建议准备阅读一下JDK的源码来提升一下自己 所以开始写JDK源码分析的文章 阅读JDK版本为1.8 目录 Object结构图 构造器 equals 方法 getClass 方法 has ...

  2. 【JDK】JDK源码分析-HashMap(2)

    前文「JDK源码分析-HashMap(1)」分析了 HashMap 的内部结构和主要方法的实现原理.但是,面试中通常还会问到很多其他的问题,本文简要分析下常见的一些问题. 这里再贴一下 HashMap ...

  3. JDK源码阅读(三):ArraryList源码解析

    今天来看一下ArrayList的源码 目录 介绍 继承结构 属性 构造方法 add方法 remove方法 修改方法 获取元素 size()方法 isEmpty方法 clear方法 循环数组 1.介绍 ...

  4. 利用IDEA搭建JDK源码阅读环境

    利用IDEA搭建JDK源码阅读环境 首先新建一个java基础项目 基础目录 source 源码 test 测试源码和入口 准备JDK源码 下图框起来的路径就是jdk的储存位置 打开jdk目录,找到sr ...

  5. JDK源码阅读-FileOutputStream

    本文转载自JDK源码阅读-FileOutputStream 导语 FileOutputStream用户打开文件并获取输出流. 打开文件 public FileOutputStream(File fil ...

  6. JDK源码阅读-FileInputStream

    本文转载自JDK源码阅读-FileInputStream 导语 FileIntputStream用于打开一个文件并获取输入流. 打开文件 我们来看看FileIntputStream打开文件时,做了什么 ...

  7. JDK源码阅读-ByteBuffer

    本文转载自JDK源码阅读-ByteBuffer 导语 Buffer是Java NIO中对于缓冲区的封装.在Java BIO中,所有的读写API,都是直接使用byte数组作为缓冲区的,简单直接.但是在J ...

  8. JDK源码阅读-RandomAccessFile

    本文转载自JDK源码阅读-RandomAccessFile 导语 FileInputStream只能用于读取文件,FileOutputStream只能用于写入文件,而对于同时读取文件,并且需要随意移动 ...

  9. JDK源码阅读-FileDescriptor

    本文转载自JDK源码阅读-FileDescriptor 导语 操作系统使用文件描述符来指代一个打开的文件,对文件的读写操作,都需要文件描述符作为参数.Java虽然在设计上使用了抽象程度更高的流来作为文 ...

随机推荐

  1. Neo4j和Elasticsearch

    Neo4j和Elasticsearch Neo4j和Elasticsearch是一种让人眼前一亮的组合,为什么需要把搜索和图表结合起来呢?它们是如何使用的呢? 在无处不在的互联网搜索引擎的推动下,全文 ...

  2. Future Parttern 先给你这张提货单

    Future是未来,预期的意思,Thread-permessage模式是指将任务交给其他线程来做,但是如果想知道处理的结果,就要使用Future模式,它的典型应用时执行一个需要花一些时间的方法,会立即 ...

  3. 安装xampp之后报错XAMPP: Starting Apache...fail.

    1.安装完成xampp之后报错: 2.网上查到的解决办法是:输入sudo apachectl stop 之后再次启动lampp,问题得以解决: 过两天发现问题并没有解决: ①在网上查询发现是因为端口被 ...

  4. MySQL回滚到某一时刻数据的方法

    MySQL回滚到某一时刻数据的方法       对于有归档日志的数据库来说,原理上都具备全库回滚到之前某一时刻的能力.在这方面最好用的Orale数据库,使用Oracle数据库的RMAN工具,可以方便的 ...

  5. 字符串——cf1109B

    /* 先判不可行的情况:n/2的是单一字符 判只切割一次能不能组成回文 枚举每个切割点,交换两个串的位置 剩下就是割两次 */ #include<bits/stdc++.h> #inclu ...

  6. C++利用动态数组实现顺序表(不限数据类型)

    通过类模板实现顺序表时,若进行比较和遍历操作,模板元素可以通过STL中的equal_to仿函数实现,或者通过回调函数实现.若进行复制操作,可以采用STL的算法函数,也可以通过操作地址实现.关于回调函数 ...

  7. Xadmin+layUi Net框架

    1.引用xadmin  layui 2.autofac+dapper+mvc 3.效果展示 4.github https://github.com/sulin888/LsProject 登录密码:Ad ...

  8. bzoj 1196: [HNOI2006]公路修建问题(二分+贪心)

    传送门 解题思路 看到最大,肯定要先想二分答案.二分之后首先从小到大枚举\(k\)个小于\(lim\)的所有一级公路,然后用并查集连到一起,然后就在剩下的里面从小到大找n-1-k个二级公路,模仿最小生 ...

  9. python实现百度OCR图片识别

    一.直接上代码 import base64 import requests class CodeDemo: def __init__(self,AK,SK,code_url,img_path): se ...

  10. 使用ProGuard混淆JAR包

    1.在Input/OutPut选项下面,add input 导入需要混淆的jar包2.点击add output,设置混淆后输出jar包的名字和路径.如下图:3.在下面的编辑区右边点击add增加要混淆的 ...