JDK源码阅读--HashMap
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的更多相关文章
- JDK源码阅读(一):Object源码分析
最近经过某大佬的建议准备阅读一下JDK的源码来提升一下自己 所以开始写JDK源码分析的文章 阅读JDK版本为1.8 目录 Object结构图 构造器 equals 方法 getClass 方法 has ...
- 【JDK】JDK源码分析-HashMap(2)
前文「JDK源码分析-HashMap(1)」分析了 HashMap 的内部结构和主要方法的实现原理.但是,面试中通常还会问到很多其他的问题,本文简要分析下常见的一些问题. 这里再贴一下 HashMap ...
- JDK源码阅读(三):ArraryList源码解析
今天来看一下ArrayList的源码 目录 介绍 继承结构 属性 构造方法 add方法 remove方法 修改方法 获取元素 size()方法 isEmpty方法 clear方法 循环数组 1.介绍 ...
- 利用IDEA搭建JDK源码阅读环境
利用IDEA搭建JDK源码阅读环境 首先新建一个java基础项目 基础目录 source 源码 test 测试源码和入口 准备JDK源码 下图框起来的路径就是jdk的储存位置 打开jdk目录,找到sr ...
- JDK源码阅读-FileOutputStream
本文转载自JDK源码阅读-FileOutputStream 导语 FileOutputStream用户打开文件并获取输出流. 打开文件 public FileOutputStream(File fil ...
- JDK源码阅读-FileInputStream
本文转载自JDK源码阅读-FileInputStream 导语 FileIntputStream用于打开一个文件并获取输入流. 打开文件 我们来看看FileIntputStream打开文件时,做了什么 ...
- JDK源码阅读-ByteBuffer
本文转载自JDK源码阅读-ByteBuffer 导语 Buffer是Java NIO中对于缓冲区的封装.在Java BIO中,所有的读写API,都是直接使用byte数组作为缓冲区的,简单直接.但是在J ...
- JDK源码阅读-RandomAccessFile
本文转载自JDK源码阅读-RandomAccessFile 导语 FileInputStream只能用于读取文件,FileOutputStream只能用于写入文件,而对于同时读取文件,并且需要随意移动 ...
- JDK源码阅读-FileDescriptor
本文转载自JDK源码阅读-FileDescriptor 导语 操作系统使用文件描述符来指代一个打开的文件,对文件的读写操作,都需要文件描述符作为参数.Java虽然在设计上使用了抽象程度更高的流来作为文 ...
随机推荐
- Python匹马行天下之python之父
龟叔和他的python 经过了漫长的旅程,终于要看到主角Python了.Python是现在非常非常流行的编程语言,在我们能看到的大部分编程语言排行榜中,Python都能在前三甲中拥有一席之地 ,并且发 ...
- batch、随机、Mini-batch梯度下降
batch梯度下降: 对所有m个训练样本执行一次梯度下降,每一次迭代时间较长: Cost function 总是向减小的方向下降. 随机梯度下降: 对每一个训练样本执行一次梯度下降,但是丢失了向量化带 ...
- more指令和less指令使用的区别
more和less都是可以一页一页的翻动 more翻页的时候,显示有百分比在最下一行 less没有 more可以用来查询 空白键 (space):代表向下翻一页:Enter :代表向下翻『一行』:/字 ...
- PHP算法之两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个数组中同样的元 ...
- Apache虚拟目录实现同一个IP绑定多个域名
在前:我使用的是Xampp,所以路径可能不同 找到apache\conf\extra\httpd-vhosts.conf, 如果没有的话请自己新建httpd-vhosts.conf文件, 并且在htt ...
- Windows ipconfig
用法: ipconfig [/allcompartments] [/? | /all | /renew [adapter] | / ...
- R语言 循环
R语言循环 可能有一种情况,当你需要执行一段代码几次. 通常,顺序执行语句. 首先执行函数中的第一个语句,然后执行第二个语句,依此类推. 编程语言提供允许更复杂的执行路径的各种控制结构. 循环语句允许 ...
- Keras+Yolo 目标检测
参考:https://www.cnblogs.com/tensorflownews/p/8922359.html Github:https://github.com/qqwweee/keras-yol ...
- https://stackoverflow.com/与程序相关的IT技术问答网站
https://stackoverflow.com/ Stack Overflow是一个与程序相关的IT技术问答网站.用户可以在网站免费提交问题,浏览问题,索引相关内容,在创建主页的时候使用简单的HT ...
- TStringList常用操作
TStringList常用操作 //TStringList 常用方法与属性: var List: TStringList; i: Integer; begin List := TStringList. ...