HashMap源码(JDK1.8)-手动注释
HashMap简介
HashMap是一种K-V映射的一种数据结构,通过K(key)值能实现在O(1)的时间复杂度下找到对应的V(value)。JDK1.8之前,HashMap的底层数据结构是数组+链表,数组中的每个元素称为一个Entry,包含(hash,key,value,next)这四个元素,其中链表是用来解决碰撞(冲突)的,如果hash值相同即对应于数组中同一一个下标,此时会利用链表将元素插入链表的尾部,(JDK1.8是头插法)。在JDK1.8及之后,底层的数据结构是:数组+(链表,红黑树),引入红黑树是为了避免链表过长,影响元素值的查找,因此当整体的数组大小大于64时,并且链表的长度大于或等于8时,会把链表转化成红黑树。在HashMap这一数据结构中,常见的方法有get、put等,在查找或者插入元素时都会建立临时数组和指针。常见的Map有:HashMap、TreeMap、LinkedHashMap、HashTable、concurrentHashMap等。掌握HashMap对于面试时很有帮助的,这是面试常问的知识点。
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;
}
//getKey
public final K getKey() {return key;}
public final V getValue() {return value;}
public final String toString() {return key + "=" + value;}
//重点,面试常备问道,求hashCode的值是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;
}
//需要重写equals,当key,value同时相等时,才相等
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;
}
//hash是key值的hashcode高低16位异或,从这里可以知道jdk1.8hashmap的key是可以为null
//若为null,取0,hashtable中的key是不能为null的
static final int hash(Object key){
int h;
return (key == null)?0:(h = key.hashCode()^(h>>>16));
}
//给出一个初始容量,会根据给定的初始容量,给出最近的2的多少平方
static final int tableSizeFor(int cap){
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMU_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
//同上,另一种实现方法,得到最近的2的多少的平方
static final int tableSizeFor(int cap){
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
return (n < 0) ? 1 : (n >= MAXIMU_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
//其中numberOfLeadingZeros方法,采用了从大到小进行判断
public static int numberOfLeadingZeros(int i){
if(i <= 0){
return i == 0 ? 32 : 0;
}
int n = 31;
if(i >= 1 << 16) {n -= 16; i >>>= 16;}
if(i >= 1 << 8) {n -= 8; i >>>= 8;}
if(i >= 1 << 4) {n -= 4; i >>>= 4;}
if(i >= 1 << 2) {n -= 2; i>>>= 2;}
return n - (i >>> 1);
}
//初始化HashMap
public HashMap(int initialCapacity, float loadFactor){
if(initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity:" + initialCapacity);
if(initialCapacity > MAXIMUM_CAPACITY){
initalCapacity = MAXIMUM_CAPACITY;
}
if(loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
this.loadFactor = loadFactor;
this.threshlod = tableSizeFor(initialCapacity);
}
//获取特定key的value值
public V get(Object key){
Node<K,V> e;
return (e = getNode(hash(key),key)) == null ? null : e.value;
}
//通过hash、key获取Node
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){
if(first.hash == hash && //总是先检查第一个结点
((k = first.key) == key || (key != null && key.equals(k))))
return first;
}
//如果不是第一个结点,则判断是否有下一个结点,接着需要判断是链表形式的还是红黑树型的
if((e = first.next) != null){
if(first instanceof 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);//进行循环,一直比对hash、key是否相等
}
return null;
}
//利用getNode进行判断
public boolean containsKey(Object key){return getNode(hash(key),key) != null;}
//put--(key,value)
public V put(K key, V value){
return putVal(hash(key),key,value,false,true);
}
//重点来看看putVal
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)//如果此时table数据为空,进行扩容
n = (tab = resize()).length;
if((p = tab[i = (n - 1)&hash]) == null) //若找到下标,此时没有值,即为null,则创建结点
tab[i] = newNode(hash,key,value,null);
else{//否则将将进行遍历链表
Node<K,V> e; K k;
//先检查头结点,如果hash,key相等,则已经插入了该结点
if(p.hash == hash &&
((k = e.key) == key) || (key != null && key.equals(k)))
e = p;
//判断插入的结点p是否是树结点
else if(p isinstanceof 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);
//如果binCount>=7时,链表树化为红黑树
if(binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab,hash);
break;
}//找到相等的结点,直接break
if(e.hash == hash && ((k = e.key) == key ||(key != null && key.equals(k))))
break;
//移动链表指针,指向下一个结点
p = e;
}
}
if(e != null){//存在映射关系,但是value为空
V oldValue = e.value;
if(!onlyIfAbsent || oldValue == value)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if(++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
final Node<K,V>[] resize(){
//定义oldTab,oldThr
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0:oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;//新的容量、新的阈值
//进行判断
if(oldCap > 0){
//直接把阈值设置为最大值,返回原来的数组
if(oldCap >= MAXIMUM_CAPACITY){
threshold = Integer.MAX_VALUE;
return oldTab;
}//容量放大两倍,阈值也放大两倍
else if((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFALUT_INITIAL_CAPACITY)
newThr = oldThr << 1; // 阈值放大两倍
}
//此时,oldCap等于零,但是阈值oldThr大于零,直接用oldThr进行替换
else if(oldThr > 0)
newCap = oldThr;//用thre替换初始容量
else {//此时,oldCap和oldThr都为零,进行初始值替换,表明第一次扩容
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACOR * DEFAULT_INITIAL)
}
//初始化阈值newThr,初始化threshold
if(newThr == 0){
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY)?(int)ft : Integer.MAX_VALUE;
}
threshold = newThr;
//建立Node型数组,对table进行赋值
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//将oldTab中元素赋值给newTab
if(oldTab != null){
for(int j = 0;j < oldCap; ++j){
Node<K,V> e;
if((e = oldTab[j]) != null){
oldTab[j] = null;//释放旧的数组中的内存
if(e.next == null) //判断原来数组位置是否只有一个节点,则进行赋值
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)//判断是树节点
((TreeNode<K,V>)e).split(this,newTab, j,oldCap);
else{ //rehash,高位等于索引+oldCap,即:j + oldCap;
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do{
next = e.next;
if((e.hash & oldCap) == 0){ //表明是原来的位置,看这个地方是否有头结点,若无直接赋值,否则从尾部插入
if(loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}//表明需要从新hash到新的位置,同理如上
else{
if(hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
}while((e = next) != null);
if(loTail != null){ //先建立一个链表,之后将这个链表接到j索引处
loTail.next = null;
newTab[j] = loHead;
} // 同理只是更改了索引位置:j + hiHead
if(hiTail != null){
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
public V remove(Object key){
Node<K,V> e;
return (e = removeNode(hash(key),key,null,false,true)) == null ? null : e.value;
}
final Node<K,V> removeNode(int hash, Object key,Object value,
boolean matchValue,boolean movable){
//定义临时变量
Node<K,V>[] tab; Node<K,V> p; int n, index;
if((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null){
Node<K,V> node = null, e; K k; V v;
//若是头结点
if(p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
//往下遍历
else if((e = p.next) != null){
if(p instanceof TreeNode)//是树形节点
node = ((TreeNode<K,V>) p).getTreeNode(hash,key);
else {
do {//进行循环遍历,找到即跳出循环
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
node = e;
break;
}
p = e;
} while((e = e.next) != null);
}
}
if(node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))){
if(node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this,tab,movale);
//若判断是头结点,直接去掉头结点,接在后面
else if(node == p)
tab[index] = node.next;
else
p.next = node.next;//在链表中间,跳过该节点
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
}
HashMap源码(JDK1.8)-手动注释的更多相关文章
- HashMap源码与相关面试题
一.哈希表 哈希表是一种可以快速定位得数据结构.哈希表可以做到平均查找.插入.删除时间是O(1),当然这是指不发生Hash碰撞得情况.而哈希表最大得缺陷就是哈希值得碰撞(collision). Has ...
- HashMap 源码详细分析(JDK1.8)
一.概述 本篇文章我们来聊聊大家日常开发中常用的一个集合类 - HashMap.HashMap 最早出现在 JDK 1.2中,底层基于散列算法实现.HashMap 允许 null 键和 null 值, ...
- 探索HashMap源码 一行一行解析 jdk1.7版本
今天我们来说一说,HashMap的源码到底是个什么? 面试大厂这方面一定会经常问到,很重要的.以jdk1.7 为标准 先带着大家过一遍 是由数组.链表组成 , 数组的优点是:每个元素有对应下标, ...
- JDK1.8 HashMap源码分析
一.HashMap概述 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时 ...
- 基于JDK1.8版本的hashmap源码笔记(二)
这一篇是接着上一篇写的, 上一篇的地址是:基于JDK1.8版本的hashmap源码分析(一) /** * 返回boolean类型的值,当集合中包含key的键值,就返回true,否则就返 ...
- 基于jdk1.8的HashMap源码学习笔记
作为一种最为常用的容器,同时也是效率比较高的容器,HashMap当之无愧.所以自己这次jdk源码学习,就从HashMap开始吧,当然水平有限,有不正确的地方,欢迎指正,促进共同学习进步,就是喜欢程序员 ...
- HashMap源码分析(基于JDK1.6)
在Java集合类中最常用的除了ArrayList外,就是HashMap了.本文尽自己所能,尽量详细的解释HashMap的源码.一山还有一山高,有不足之处请之处,定感谢指定并及时修正. 在看Hash ...
- HashMap 源码分析 基于jdk1.8分析
HashMap 源码分析 基于jdk1.8分析 1:数据结构: transient Node<K,V>[] table; //这里维护了一个 Node的数组结构: 下面看看Node的数 ...
- 死磕Java之聊聊HashMap源码(基于JDK1.8)
死磕Java之聊聊HashMap源码(基于JDK1.8) http://cmsblogs.com/?p=4731 为什么面试要问hashmap 的原理
- HashMap源码分析-jdk1.7
注:转载请注明出处!!!!!!!这里咱们看的是JDK1.7版本的HashMap 学习HashMap前先知道熟悉运算符合 *左移 << :就是该数对应二进制码整体左移,左边超出的部分舍弃,右 ...
随机推荐
- linux下网络设置和远程连接
配置ip.子网掩码.静态设置.开机启动ONBOOT网卡 /etc/sysconfig/network-scripts/ifcfg-eth0 重启网络 service network restart ...
- git 中.gitignore文件不生效
.gitignore文件 新增忽略文件并没有生效 新增的忽略文件没有生效,是因为git是有缓存的,而之前的文件在缓存中,并不会清除掉,还会继续提交,所以更新.gitignore文件,要清除缓存文件 g ...
- LeetCode24 两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表. 示例: 给定 1->2->3->4, 你应该返回 2->1->4->3. 说明: 你的算法只能使用常数的 ...
- 【Docker】在Linux系统中安装Docker虚拟机、启动停止重启查看Docker命令
安装Docker 文章目录 安装Docker Docker虚拟机架构 在Linux系统中安装Docker虚拟机 管理Docker虚拟机 参考资料 Docker虚拟机架构 Docker创建的所有虚拟实例 ...
- QPainter 绘制图像接口
阅读本文大概需要 3 分钟 我们在开发软件的过程中,绘制图像功能必不可少,使用 Qt 绘制图像时非常简单,只需要传递几个参数就可以实现功能,在 Qt 中绘制图像的 api有好几个 void drawI ...
- 【Linux】rsync中sending incremental file list时间优化
每次使用rsync的时候,前面出现sending incremental file list 这句之后要等待很长时间 查了很多帖子和官方文档后,发现是-c这个选项的问题, -v, --verbose ...
- kubernetes机理之调度器以及控制器
一 了解调度器 1.1 调度器是如何将一个pod调度到节点上的 我们都已然知晓了,API服务器不会主动的去创建pod,只是拉起系统组件,这些组件订阅资源状态的通知,之后创建相应的资源,而负责调度po ...
- 实操|如何将 Containerd 用作 Kubernetes runtime
日前专为开发者提供技术分享的又拍云 OpenTalk 公开课邀请了网易有道资深运维开发工程师张晋涛,直播分享<Containerd 上手实践 >,详细介绍 Containerd 的发展历程 ...
- SAP系统跨平台字符编码转换
SAP系统在进行了夸平台的迁移,可能会遇到操作系统层文件编码不同,导致SAP系统无法识别或者乱码的问题.例如SAP系统从AIX平台迁移到linux平台,SAP应用服务器的编码会发生变化,从4102变化 ...
- Py迭代和迭代器,生成器,生产者和消费者模型
迭代器iter 1.迭代的含义: 每次生成的结果依赖于上一次.问路,先问第一个人,第一个人不知道他就说第二个人知道,然后去找第二个人.第二个人不知道就说第三个人知道,然后去找第三个人 2.递归的含义: ...