基于JDK1.8版本的hashmap源码分析(一)
今天看了下hashmap中的源码,下面列出一些自己的收获
开头,public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { //继承子AbstractMap类,实现了克隆和序列化
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //表示初始化定义的容量是16,通过移位实现的,1是2^0次方,左移4位就是2^4=16,这个值必须是2的次方
static final int MAXIMUM_CAPACITY = 1 << 30;//定义的最大容量,是2^30次方,这个值必须是2的次方(计算机采用二进制处理数据,二进制就2个数据0,1,可以表示多种含义,实现简单)
static final float DEFAULT_LOAD_FACTOR = 0.75f;这个是加载因子,表示容器被使用75%时,就进行扩容了。是一种容器使用量的控制
static final int TREEIFY_THRESHOLD = 8;表示当链表的长度为8时就把链表转换为红黑树,此值是定义的临界转换值
static final int UNTREEIFY_THRESHOLD = 6;表示重新设置把树改为链表,当长度小于6时
static final int MIN_TREEIFY_CAPACITY = 64;桶被树形化时的表最小容量,或者决定对桶进行扩容。即根据hash表中的数据决定是树形化还是扩容。
static class Node<K,V> implements Map.Entry<K,V> {//此处是定义静态内部类的结点,需要hash值(用于计算存放到hashmap中的哪个位置),key值(标识),value,next(下一个结点)
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;
} public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
/**
*此处是重写Object类中的hashCode值,为了是分布均匀,尽可能减少冲突,不保证唯一,但同一个类,必须有唯一的hashcode,不同的对象可以有相同的hashcode值
*使用key和value的值进行hashcode值的异或决定放到桶中的哪个位置上
*/
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
} public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
/**一般重写hashcode方法后,都会重写equals方法,2个都一起进行修改,equals具体是比较两个对象是否相等,==是比较地址是否相等的
*而不同的对象一定会有不同的内存地址,导致2对象的比较一定不等,重写Object类中的equals方法后(Object类中直接比较地址),会在instanceof判断是同一个类型后,比较对象内部的属性
*若属性相同,则判断这两个对象是相等的,equals方法返回true,当然前提是hashcode定位到桶的哪个位置,在桶的链表存储中使用equals进行具体的对象比较。
*/
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值不为空时,使用key值与key无符号右移16位的值进行按位异或的操作得到的结果。可以进来得到高低hash值的分布,高位与地位都有数据,
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
} /**
*此处定义的是静态类:比较器,主要是返回参数对象x的类
*前提是参数对象要实现了Comparable接口,否则返回空,
*/
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) { //判断x是否是实现了Comparable接口,若没有实现此接口,则直接返回null
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
if ((c = x.getClass()) == String.class) // bypass checks
return c;
if ((ts = c.getGenericInterfaces()) != null) {
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
/**
*此处是返回对象k,x的比较结果,
*前提是对象x的类不匹配kc的类,否则返回结果0
*/
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareTo(x));
} /**
*此处是实现表的大小设置,
*n无符号右移,移位后与本身按位或,依次是无符号右移2的n次方,最终返回的是表的设置大小。
*/
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 >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
} transient Node<K,V>[] table;//表在第一次使用时初始化,长度是2的N次方
/**
* 用于存储结点的定义
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
*一整个map中存储多少个key-value键值对,map中存储的都是key-value映射关系的数据
*/
transient int size;
/**
* 下一次容器扩容的大小.
*/
int threshold;
/**
* hash表的加载因子,是说明当容器使用了(容量*加载因子)的容量时,进行扩容,默认的加载因子是0.75,意思是当容器的容量是100时,当使用了75个就可以进行扩容了,扩容后面会有说明
*/
final float loadFactor; /**
* 使用构造器构造一个空的hashmap对象,使用自定义的初始容量和加载因子
*
* @param 初始化容量
* @param 加载因子
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) //初始化容器的容量不能小于0
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)//初始化容器的容量不能大于最大值。
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))//加载因子不能小于等于0,也不能是未确定的
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
} //以下几个构造器就不再重复说明了 /**
* 实现在map中放入对象,
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size(); //获取当前map的空间大小
if (s > 0) { //若hashmap大于0
if (table == null) { // 检查表是否是还没有初始化
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) //若表已经初始化,且hash表的大小大于定义的值,就会重新进行设置大小
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {//循环把对象放入到map中
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);//实现具体放到map中的方法,后续会有说明
}
}
} //返回map中key对象的value值
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
} /**具体实现怎么根据key值获取具体的value值,传入的有hash值和key,
*根据hash值可以计算出具体的table中的哪个下标,使用的计算方式是:(n - 1) & hash,n代表数组的长度,效果和hash%n是一样的效果,不过移位计算的效果更好
*先查找第一个结点是不是相等的,若相等,则直接返回,若不是则判断是不是树结点,若是,则循环树,获取具体的结点信息
*最后,循环遍历链表,在链表中返回对应key的结点信息
*/
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 && // 每次都会检测都first结点
((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 {//循环链表,找出匹配的key值,返回对象的引用结点
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
基于JDK1.8版本的hashmap源码分析(一)的更多相关文章
- 基于JDK1.8版本的hashmap源码笔记(二)
这一篇是接着上一篇写的, 上一篇的地址是:基于JDK1.8版本的hashmap源码分析(一) /** * 返回boolean类型的值,当集合中包含key的键值,就返回true,否则就返 ...
- 基于JDK1.8,Java容器源码分析
容器源码分析 如果没有特别说明,以下源码分析基于 JDK 1.8. 在 IDEA 中 double shift 调出 Search EveryWhere,查找源码文件,找到之后就可以阅读源码. Lis ...
- JDK1.7 中的HashMap源码分析
一.源码地址: 源码地址:http://docs.oracle.com/javase/7/docs/api/ 二.数据结构 JDK1.7中采用数组+链表的形式,HashMap是一个Entry<K ...
- Jdk1.8下的HashMap源码分析
目录结构 一.面试常见问题 二.基本常量属性 三.构造方法 四.节点结构 4.1 Node类 4.2.TreeNode 五.put方法 5.1 key的has ...
- Jdk1.7下的HashMap源码分析
本文主要讨论jdk1.7下hashMap的源码实现,其中主要是在扩容时容易出现死循环的问题,以及put元素的整个过程. 1.数组结构 数组+链表 示例图如下: 常量属性 /** * The defau ...
- HashMap 源码分析 基于jdk1.8分析
HashMap 源码分析 基于jdk1.8分析 1:数据结构: transient Node<K,V>[] table; //这里维护了一个 Node的数组结构: 下面看看Node的数 ...
- 源码分析系列1:HashMap源码分析(基于JDK1.8)
1.HashMap的底层实现图示 如上图所示: HashMap底层是由 数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...
- JDK1.8 HashMap源码分析
一.HashMap概述 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时 ...
- 【JAVA集合】HashMap源码分析(转载)
原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储 ...
随机推荐
- Oarcle之用户管理 与 DCL
用户管理 1.创建一个账户 create user zhangsan identified by123456: 2.修改账户的密码 alter user zhangsan identified by ...
- 51nod 1009 数字1的数量(数位dp模板)
给定一个十进制正整数N,写下从1开始,到N的所有正数,计算出其中出现所有1的个数. 例如:n = 12,包含了5个1.1,10,12共包含3个1,11包含2个1,总共5个1. 数位dp的模板题 ...
- Jquery实现checkbox按shift多选
html <html> <head> <meta http-equiv="content-type" content="text/html; ...
- JSOI2020备考知识点复习
我太菜了qaq,我好爱咕咕咕啊 在NOIP2018爆炸后,我只能指望着在JSOI2019JSOI2020上咸鱼翻身(flag*1) 所以,我要开始复习学习(flag*2) 此博客文会不定时更新qaq( ...
- C# 为所有 CheckBox 添加事件
C# 为 form 窗体中的所有相同组件循环添加相同事件,这样减少了代码量. private void Form2_Load(object sender, EventArgs e) { foreach ...
- 算法(第四版)C# 习题题解——1.4
写在前面 整个项目都托管在了 Github 上:https://github.com/ikesnowy/Algorithms-4th-Edition-in-Csharp 这一节内容可能会用到的库文件有 ...
- lsyncd+rsync配置图片资源双向同步
需求:为保证国内外图片加载速度,国内请求上传图片资源地址阿里云oss,国外请求上传图片资源地址aws s3,为保证图片资源的一致性,需定时进行oss和s3图片双向同步 调研方案:由于之前配置过inot ...
- HTTP简单解析
一.简介 HTTP是一种基于TCP/IP的超文本传输协议,用于从WWW服务器传输超文本到本地浏览器. HTTP是一种基于客户端/服务器(C/S架构)的无状态.无连接.媒体独立的传输协议. HTTP是一 ...
- HBase Filter及对应Shell
比较运算符 CompareFilter.CompareOp比较运算符用于定义比较关系,可以有以下几类值供选择: EQUAL 相等 GREATER 大于 GREATER_OR_EQUAL 大于等于 LE ...
- 解决Win10 PowerShell无法激活Anaconda环境的问题
最近在使用Anaconda的过程中,发现在Win10的PowerShell在使用conda activate 环境名激活环境时无效,而CMD则可以.这里前提必须将Anaconda 写入环境变量.否则在 ...