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)-手动注释的更多相关文章

  1. HashMap源码与相关面试题

    一.哈希表 哈希表是一种可以快速定位得数据结构.哈希表可以做到平均查找.插入.删除时间是O(1),当然这是指不发生Hash碰撞得情况.而哈希表最大得缺陷就是哈希值得碰撞(collision). Has ...

  2. HashMap 源码详细分析(JDK1.8)

    一.概述 本篇文章我们来聊聊大家日常开发中常用的一个集合类 - HashMap.HashMap 最早出现在 JDK 1.2中,底层基于散列算法实现.HashMap 允许 null 键和 null 值, ...

  3. 探索HashMap源码 一行一行解析 jdk1.7版本

    今天我们来说一说,HashMap的源码到底是个什么? 面试大厂这方面一定会经常问到,很重要的.以jdk1.7 为标准    先带着大家过一遍 是由数组.链表组成 , 数组的优点是:每个元素有对应下标, ...

  4. JDK1.8 HashMap源码分析

      一.HashMap概述 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时 ...

  5. 基于JDK1.8版本的hashmap源码笔记(二)

    这一篇是接着上一篇写的, 上一篇的地址是:基于JDK1.8版本的hashmap源码分析(一)     /**     * 返回boolean类型的值,当集合中包含key的键值,就返回true,否则就返 ...

  6. 基于jdk1.8的HashMap源码学习笔记

    作为一种最为常用的容器,同时也是效率比较高的容器,HashMap当之无愧.所以自己这次jdk源码学习,就从HashMap开始吧,当然水平有限,有不正确的地方,欢迎指正,促进共同学习进步,就是喜欢程序员 ...

  7. HashMap源码分析(基于JDK1.6)

      在Java集合类中最常用的除了ArrayList外,就是HashMap了.本文尽自己所能,尽量详细的解释HashMap的源码.一山还有一山高,有不足之处请之处,定感谢指定并及时修正. 在看Hash ...

  8. HashMap 源码分析 基于jdk1.8分析

    HashMap 源码分析  基于jdk1.8分析 1:数据结构: transient Node<K,V>[] table;  //这里维护了一个 Node的数组结构: 下面看看Node的数 ...

  9. 死磕Java之聊聊HashMap源码(基于JDK1.8)

    死磕Java之聊聊HashMap源码(基于JDK1.8) http://cmsblogs.com/?p=4731 为什么面试要问hashmap 的原理

  10. HashMap源码分析-jdk1.7

    注:转载请注明出处!!!!!!!这里咱们看的是JDK1.7版本的HashMap 学习HashMap前先知道熟悉运算符合 *左移 << :就是该数对应二进制码整体左移,左边超出的部分舍弃,右 ...

随机推荐

  1. pxe过程和原理

    pxe过程和原理 概要 远程安装和启动操作系统 网卡固件支持pxe的接口,一般是有基本的ip/udp协议栈,支持dhcp, tftp协议:bios中可以设置通过pxe启动操作系统 启动过程,大致如下: ...

  2. hadoop集群中zkfc的作用和工作过程

    一. 简单了解NameNode的ZKFC机制 NameNode的HA可以个人认为简单分为共享editLog机制和ZKFC对NameNode状态的控制 一般导致NameNode切换的原因 ZKFC的作用 ...

  3. java的重载与重写

    原文链接http://zhhll.icu/2020/11/11/java%E5%9F%BA%E7%A1%80/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/%E9%87%8 ...

  4. WPF学习笔记02_布局

    布局原则 WPF窗口只能包含单个元素.如果要放置多个元素,需要放置一个容器,然后在容器中添加元素. 不应显示的设定元素的尺寸 不应该使用屏幕坐标指定元素的位置 布局容器的子元素"共享&quo ...

  5. TR2021_0000偶发数据库连接异常问题排查

    [问题描述] 数据库连接异常是很难排查的一类问题.因为它牵涉到应用端,网络层和服务器端.任何一个组件异常,都会导致数据库连接失败.开发遇到数据库连接不上的问题,都会第一时间找DBA来协助查看,DBA除 ...

  6. SpringBoot项目,如何优雅的把接口参数中的空白值替换为null值?

    问题发生 我们公司代码生成的时候,查询列表统一都是使用了setEntity() ,查询写法如下: public List<BasReservoirArea> selectList(BasR ...

  7. Flutter 基础组件:单选框和复选框

    前言 Material组件库中提供了Material风格的单选开关Switch和复选框Checkbox,虽然它们都是继承自StatefulWidget,但它们本身不会保存当前选中状态,选中状态都是由父 ...

  8. 基于 MPI/OpenMP 混合编程的大规模多体(N-Body)问题仿真实验

    完整代码: #include <iostream> #include <ctime> #include <mpi.h> #include <omp.h> ...

  9. kafka(二)基本使用

    一.Kafka线上集群部署方案 既然是集群,那必然就要有多个Kafka节点机器,因为只有单台机器构成的kafka伪集群只能用于日常测试之用,根本无法满足实际的线上生产需求. 操作系统: kafka由S ...

  10. jQuery 真伪数组的转换

    //真数组转换伪数组 var arr = [1,3,5,7,9]; var obj = {}; [].push.apply(obj,arr); console.log(obj) //伪数组转真数组 v ...