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. Python匹马行天下之python之父

    龟叔和他的python 经过了漫长的旅程,终于要看到主角Python了.Python是现在非常非常流行的编程语言,在我们能看到的大部分编程语言排行榜中,Python都能在前三甲中拥有一席之地 ,并且发 ...

  2. batch、随机、Mini-batch梯度下降

    batch梯度下降: 对所有m个训练样本执行一次梯度下降,每一次迭代时间较长: Cost function 总是向减小的方向下降. 随机梯度下降: 对每一个训练样本执行一次梯度下降,但是丢失了向量化带 ...

  3. more指令和less指令使用的区别

    more和less都是可以一页一页的翻动 more翻页的时候,显示有百分比在最下一行 less没有 more可以用来查询 空白键 (space):代表向下翻一页:Enter :代表向下翻『一行』:/字 ...

  4. PHP算法之两数之和

    给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个数组中同样的元 ...

  5. Apache虚拟目录实现同一个IP绑定多个域名

    在前:我使用的是Xampp,所以路径可能不同 找到apache\conf\extra\httpd-vhosts.conf, 如果没有的话请自己新建httpd-vhosts.conf文件, 并且在htt ...

  6. Windows ipconfig

    用法:    ipconfig [/allcompartments] [/? | /all |                                 /renew [adapter] | / ...

  7. R语言 循环

    R语言循环 可能有一种情况,当你需要执行一段代码几次. 通常,顺序执行语句. 首先执行函数中的第一个语句,然后执行第二个语句,依此类推. 编程语言提供允许更复杂的执行路径的各种控制结构. 循环语句允许 ...

  8. Keras+Yolo 目标检测

    参考:https://www.cnblogs.com/tensorflownews/p/8922359.html Github:https://github.com/qqwweee/keras-yol ...

  9. https://stackoverflow.com/与程序相关的IT技术问答网站

    https://stackoverflow.com/ Stack Overflow是一个与程序相关的IT技术问答网站.用户可以在网站免费提交问题,浏览问题,索引相关内容,在创建主页的时候使用简单的HT ...

  10. TStringList常用操作

    TStringList常用操作 //TStringList 常用方法与属性: var List: TStringList; i: Integer; begin List := TStringList. ...