hashMap源码解析(四)
---恢复内容开始---
在上文中讲到了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)
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); //可以发现,在jdk1.8中,当hash值相等时,最后插入的元素将会被安置在链的尾端
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;
}
红黑树后面章节会单独拿出来讲,这里先忽略.
这里需要留意标红的两个钩子函数.
源码如下:
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
这个三个方法的实现是在子类LinkedHashMap中,顾名思义,三个函数的作用分别是:节点访问后,节点插入后,节点移除后 做一些操作.
void afterNodeRemoval(Node<K,V> e) { // unlink //从链表中移除(这个函数是在移除节点后调用的,就是将节点从双向链表中删除。)
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
void afterNodeInsertion(boolean evict) { // possibly remove eldest //(// 如果定义了移除规则,则执行相应的溢出)
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {//如果定义了accessOrder,则保证了最近访问的节点(添加的节点)位于最后
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
我们从上面3个函数看出来,基本上都是为了保证双向链表中的节点次序或者双向链表容量所做的一些额外的事情,目的就是保持双向链表中节点的顺序要从eldest到youngest。
以上关于钩子函数的内容暂不深究,等研究LinkedHashMap源码时再去探讨.
以上putval方法也是插入元素的put方法.以下为获取元素的get方法:
源码:
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 && // always check first node
((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);
}
}
return null;
}
get方法相对于put方法要简单的多,简单理解就是 通过key的hash值 获取数组位置,再遍历该位置的链表,获取想要的元素.唯一需要留意的是红黑树部分,这部分后面章节会单独拎出来讲.
删除remove方法:
源码:
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
/**
* Implements Map.remove and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to match if matchValue, else ignored
* @param matchValue if true only remove if value is equal
* @param movable if false do not move other nodes while removing
* @return the node, or null if none
*/
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, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
这里可以稍微注意下 注释.
matchValue:if true ,只有value相等才删除;
movable: if false ,不移动其它节点的位置.
这里通过获取想要删除的节点e,通过node,和意义相同的节点p.next(p.next = node), 以p.next=node.next 的方式,讲节点node删除掉.
hashMap源码解析(四)的更多相关文章
- 【转】Java HashMap 源码解析(好文章)
.fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...
- Java中的容器(集合)之HashMap源码解析
1.HashMap源码解析(JDK8) 基础原理: 对比上一篇<Java中的容器(集合)之ArrayList源码解析>而言,本篇只解析HashMap常用的核心方法的源码. HashMap是 ...
- HashMap源码解析 非原创
Stack过时的类,使用Deque重新实现. HashCode和equals的关系 HashCode为hash码,用于散列数组中的存储时HashMap进行散列映射. equals方法适用于比较两个对象 ...
- Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?
Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的? 如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一 ...
- Sentinel源码解析四(流控策略和流控效果)
引言 在分析Sentinel的上一篇文章中,我们知道了它是基于滑动窗口做的流量统计,那么在当我们能够根据流量统计算法拿到流量的实时数据后,下一步要做的事情自然就是基于这些数据做流控.在介绍Sentin ...
- 最全的HashMap源码解析!
HashMap源码解析 HashMap采用键值对形式的存储结构,每个key对应唯一的value,查询和修改的速度很快,能到到O(1)的平均复杂度.他是非线程安全的,且不能保证元素的存储顺序. 他的关系 ...
- HashMap源码解析和设计解读
HashMap源码解析 想要理解HashMap底层数据的存储形式,底层原理,最好的形式就是读它的源码,但是说实话,源码的注释说明全是英文,英文不是非常好的朋友读起来真的非常吃力,我基本上看了差不多 ...
- 详解HashMap源码解析(下)
上文详解HashMap源码解析(上)介绍了HashMap整体介绍了一下数据结构,主要属性字段,获取数组的索引下标,以及几个构造方法.本文重点讲解元素的添加.查找.扩容等主要方法. 添加元素 put(K ...
- 【Java深入研究】9、HashMap源码解析(jdk 1.8)
一.HashMap概述 HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现.与HashTable主要区别为不支持同步和允许null作为key和value.由于HashMap不是线程 ...
- HashMap 源码解析(一)之使用、构造以及计算容量
目录 简介 集合和映射 HashMap 特点 使用 构造 相关属性 构造方法 tableSizeFor 函数 一般的算法(效率低, 不值得借鉴) tableSizeFor 函数算法 效率比较 tabl ...
随机推荐
- EEPROM
EEPROM (Electrically Erasable Programmable read only memory),带电可擦可编程只读存储器--一种掉电后数据不丢失的存储芯片. EEPROM 可 ...
- Remote debugger is in a background tab which may cause apps to perform slowly. Fix this by foregrounding the tab (or opening it in a separate window).
先上代码: /** * Sample React Native App * https://github.com/facebook/react-native * * @format * @flow * ...
- 关于Jedis是否线程安全的测试
转: 关于Jedis是否线程安全的测试 2018年09月20日 15:53:51 cwz_茶仔 阅读数:659 版权声明:转载请注明出处 https://blog.csdn.net/jk94043 ...
- mysql 变量定义 sql查询
SET @idnoStr:='"idNo":"'; SELECT LOCATE(@idnoStr, param_array), LOCATE('",', par ...
- CentOS6.9快速安装配置svn
CentOS6.9快速安装配置svn 环境介绍: 操作系统:CentOS release 6.9 (Final)192.168.65.130 (svn服务器)192.168.65.129 (svn客户 ...
- jenkins配置小结
启动jenkins:javaw -jar -Dhudson.model.DirectoryBrowserSupport.CSP= jenkins.war --httpPort=8001 wget h ...
- [DUBBO] Unexpected error occur at send statistic, cause: Forbid consumer 192.168.3.151 access servic
[DUBBO] Unexpected error occur at send statistic, cause: Forbid consumer 192.168.3.151 access servic ...
- nginx中间件
Nginx简介 Nginx是一个开源且高性能.可靠的HTTP中间件.代理服务.其特点是占有内存少,并发能力强. Nginx优势:IO多路复用epoll 1.什么是IO复用 它是内核提供的一种同时监控多 ...
- MongoDB硬件及开发标准规范
大数据平台部 运维研发组 MongoDB硬件及开发标准规范 说明: 无特殊情况,均以此文档为参考文件搭建,如有特殊情况,需与运维研发组和开发组商议后进行更改. MongoD ...
- CentOS7 上以 RPM 包方式安装 Oracle 18c 单实例
安装阿里云 YUM 源 https://opsx.alibaba.com/mirror?lang=zh-CN 一.安装Oracle数据库 1.安装 Oracle 预安装 RPM yum -y loca ...