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前先知道熟悉运算符合 *左移 << :就是该数对应二进制码整体左移,左边超出的部分舍弃,右 ...
随机推荐
- [Abp]Abp 新手入门随记
项目结构说明 *.Application 应用服务实现 *.Application.Contracts 包含DTO及应用服务接口 *.DbMigrator 数据迁移项目 开发和生产环境迁移数据库架构和 ...
- 小米11和iphone12参数对比哪个好
小米11:搭载最新一代三星的AMOLED屏幕,120Hz屏幕刷新,iPhone12使用全新一代的视网膜屏,6.1英寸屏幕,支持60Hz屏幕刷新,支持HDR显示,P3广色域小米手机爆降800 优惠力度空 ...
- MySQL更新勿用and
项目实战 一次错误的更新 更新前的数据 执行更新语句 然后我们查看下更新后的数据,发现居然数据为空? 使用主键id的方式查询这条数据,发现需要更新的手机号码居然变为了0 当我们把更新语句中的and ...
- PHP 清除缓存文件
/*清除缓存文件*/ public function clearRuntime() { $this->delFileByDir(RUNTIME_PATH); $this->success( ...
- 【Azure Developer】使用Postman获取Azure AD中注册应用程序的授权Token,及为Azure REST API设置Authorization
Azure Active Directory (Azure AD) is Microsoft's cloud-based identity and access management service, ...
- vue中选中弹出框内的表格
一:可多选情况且对应勾选 由于是弹出框形式,所以会出现新增DOM与数据的改变问题,因此要使用$nextTick,不然一开始弹出得时候DOM还没有生成,却要获取DOM会报错:这种多选情况会出现一个bug ...
- Flutter 基础组件:文本、字体样式
// 文本.字体样式 import 'package:flutter/material.dart'; class TextFontStyle extends StatelessWidget { // ...
- 【Linux】kali 安装 python3 和 pip3(亲测有效)
[Linux]kali 安装 python3 和 pip3 引言: 在使用kali的时候,经常会用到各种工具以及脚本,而大多数脚本都是以python编写的,但是烦就烦在python有2个版本,有些 ...
- Lniux 入门:03 用户及文件权限管理
1.1 实验内容 Linux 中创建.删除用户,及用户组等操作. Linux 中的文件权限设置. 1.2 实验知识点 Linux 用户管理 Linux 权限管理 通过第一节课程的学习,你应该已经知道, ...
- Linux学习笔记 | 常见错误之VMware启动linux后一直黑屏
方法1: 宿主机(windows)管理员模式运行cmd 输入netsh winsock reset 然后重启电脑 netsh winsock reset命令,作用是重置 Winsock 目录.如果一台 ...