调试JDK源代码-一步一步看HashMap怎么Hash和扩容
调试JDK源代码-一步一步看HashMap怎么Hash和扩容
调试JDK源代码-ConcurrentHashMap实现原理
调试JDK源代码-调试JDK源代码-Hashtable实现原理以及线程安全的原因
还是调试源代码最好。
开发环境 JDK1.8+NetBeans8.1
说明:调试HashMap的 public V put(K key, V value) 方法并查看key的值时不能显示变量的值,原因在于oracle提供的jre中rt.jar不带debug信息。
orcale在编译src时使用了 javac -g:none。意思是不带不论什么调试信息,这样能够减小rt.jar的大小。若想正常调试jdk,就仅仅能又一次编译src.zip。
当然也能够仅仅编译单个须要关注的java就可以,比如HashMap.java。
一.解压src.zip
解压src.zip到E:\workspace\下,
src.zip在安装的C:\Program Files\Java\jdk1.8.0_25下
二.javac -g重编译
又一次编译src\java\util下的HashMap.java
Windows下进入DOS环境。输入
E:\workspace\src\java\util
然后再输入E:就直接到了E:\workspace\src\java\util
默认假设不带-g编译是没有调试信息是不够的。
# javac -g HashMap.java
三.替换rt.jar
将编译好的全部的HashMap.class都放入C:\Program Files\Java\jdk1.8.0_25\jre\lib的rt.jar
说明:须要做好备份以防搞错。
四.调试HashMap
先看看HashMap的理论吧
import java.util.HashMap;
import java.util.Map;
import org.junit.Test; public class TestHash { @Test
public void testHashMap() throws Exception {
System.out.println("==========================");
Map<String, String> m = new HashMap<String, String>();
for (int i = 0; i < 18; i++) {
m.put((char) (i + 65) + (char) (i + 66) + (char) (i + 67) + "", i + ">>>http://blog.csdn.net/unix21/");
}
System.out.println("==========================");
}
}
以下是源代码
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
} /**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
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);
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;
}
1.第一次进入源代码
先初始化增长因子
一開始声明一个
transient Node<K,V>[] table;
java 的transientkeyword为我们提供了便利,你仅仅须要实现Serilizable接口,将不须要序列化的属性前加入keywordtransient。序列化对象的时候,这个属性就不会序列化到指定的目的地中。
函数体内声明一个Node<K,V>[] tab
一開始table=null。所以tab也是null的
能够看到n=16。假设不使用-g编译是看不到n的,这说明初始的tab长度是16。
然后给tab进行初始化。p=tab[0]=null
2.插入newNode
终于会调用static class Node<K,V>的Node(int hash, K key, V value, Node<K,V> next)
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
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;
} public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return 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;
} 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;
}
}
第一个Node节点就有值了。其next为null.
关于静态嵌套类
3.回到putVal
tab[0]就是返回的Node
4.查看是否须要扩容
还不到threshold的上限12 ,所以无需扩容。
5.HashMap第二次put进入putVal
非常显然这个时候table不为空,由于前次已经插值了。
i=3,p=tab[3]
新的node插入在tab[3]上,此次依旧无需扩容。
第4次插值
第7次插值
第11次
第12次
第13次
tab和
此次须要扩容
点开oldTab
下一步
下一步
下一步。threshold升为24
下一步
newTab
oldTab
oldTab[0]
oldTab[j] = null;
下一步
下一步next = e.next;
下一步
下一步
下一步
下一步(e = next) != null
下一步
经过N此循环之后
newTab
oldTab
回到putVal
扩容之后再次进入第14次进入
tab
关于HashMap就分析到此,网上有几篇写的不错的帖子结合看看就更明确了,建议阅读下:
深入Java集合学习系列:HashMap的实现原理 引文 深入Java集合学习系列:HashMap的实现原理 原文
HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容。loadFactor的默认值为0.75,这是一个折中的取值。
也就是说。默认情况下。数组大小为16。那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32。即扩大一倍。
然后又一次计算每一个元素在数组中的位置,而这是一个很消耗性能的操作,所以假设我们已经预知HashMap中元素的个数,那么预设元素的个数可以有效的提高HashMap的性能。
调试JDK源代码-一步一步看HashMap怎么Hash和扩容的更多相关文章
- 如何调试JDK源代码并查看局部变量值
如下图: 按F5进入String.startsWith,如下: 点“Edit Source Lookup Path” 附加源代码,如下图: 附加上源代码后如下: 可以看到,当鼠标放在“prefix”上 ...
- Eclipse/Myeclipse中查看和调试JDK源代码的方法
看过这篇文章后,实践写的 http://blog.csdn.net/qq_27857857/article/details/71250401 一共做了以下几部: 第一步: 第二步: 一直next,到第 ...
- JDK源代码学习-ArrayList、LinkedList、HashMap
ArrayList.LinkedList.HashMap是Java开发中非常常见的数据类型.它们的区别也非常明显的,在Java中也非常具有代表性.在Java中,常见的数据结构是:数组.链表,其他数据结 ...
- 在ASP.NET 5项目中使用和调试外部源代码包
(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:由于在ASP.NET 5中,项目依赖都是通过"包"来引用,所以使用 ...
- 从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码
首发于公众号:计算机视觉life 旗下知识星球「从零开始学习SLAM」 这可能是最清晰讲解g2o代码框架的文章 理解图优化,一步步带你看懂g2o框架 小白:师兄师兄,最近我在看SLAM的优化算法,有种 ...
- 转:IDEA中如何使用debug调试项目 一步一步详细教程
原文链接:http://www.yxlzone.top/show_blog_details_by_id?id=2bf6fd4688e44a7eb560f8db233ef5f7 在现在的开发中,我们经常 ...
- IDEA中如何使用debug调试项目 一步一步详细教程
转载该文章:https://blog.csdn.net/yxl_1207/article/details/80973622 一.Debug开篇 首先看下IDEA中Debug模式下的界面. 如下是在ID ...
- (原创)超详细一步一步在eclipse中配置Struts2环境,无基础也能看懂
(原创)超详细一步一步在eclipse中配置Struts2环境,无基础也能看懂 1. 在官网https://struts.apache.org下载Struts2,建议下载2.3系列版本.从图中可以看出 ...
- configure - 源代码安装的第一步
configure是源代码安装的第一步,主要的作用是对即将安装的软件进行配置,检查当前的环境是否满足要安装软件的依赖关系 configure有许多参数可配,具体参见./configure --help ...
随机推荐
- 求助:可以使用任何编程工具做成一个控件或组件,使得在VB中能调用并得到摄像头的参数及图片。
请看下网址上的这个问题,看是否有解决的方式http://www.educity.cn/wenda/338634.html
- 使ThinkPHP(3.2.3)的分页类支持Bootstrap风格
ThinkPHP 3.2.3自带的分页类位于:/ThinkPHP/Library/Think/Pages.class.php ,官方文档在这里:ThinkPHP3.2.3数据分页 Pages.clas ...
- 你的宽带ip地址被100.64了吗?
你的宽带ip地址被100.64了吗? 最近需要用外网的时候发现,宿舍路由wan口的ip变成了100.64.X.X,本以为是一个外网的ip,可事实上并不是,并且从外网无法直接访问. 首先,我们都 ...
- [Tensorflow] 使用 model.save_weights() 保存 Keras Subclassed Model
import numpy as np import matplotlib.pyplot as plt import os import time import tensorflow as tf tf. ...
- JAVA程序员面试笔试宝典2
1.Java集合框架 2.迭代器 使用容器的iterator()方法返回一个iterator,然后通过iterator的next()方法返回第一个元素 使用iterator的hasnext()方法判断 ...
- JavaScipt30(第一个案例)(主要知识点:键盘事件以及transitionend)
今天得到一个github练习项目,是30个原生js写成的小例子,麻雀虽小五脏俱全,现在记录一下第一个. 第一个是键盘按键时页面上对应的键高亮,同时播放音频,松开后不再高亮. 我自己实现了一下,然后查看 ...
- Eigen库笔记整理(二)
Eigen/Geometry 模块提供了各种旋转和平移的表示 旋转矩阵直接使用 Matrix3d 或 Matrix3f Eigen::Matrix3d rotation_matrix = Eigen: ...
- 服务器 获取用户 真实ip
在有代理的情况下,因为要代替客户端去访问服务器,所以,当请求包经过反向代理后,在代理服务器这里这个IP数据包的IP包头做了修改,最终后端WEB服务器得到的数据包的头部源IP地址是代理服务器的IP地址. ...
- php第二十五节课
详情删除 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3 ...
- Vue.js 模板语法
本章节将详细介绍 Vue.js 模板语法,如果对 HTML +Css +JavaScript 有一定的了解,学习起来将信手拈来. Vue.js 使用了基于 HTML 的模版语法,允许开发者声明式地将 ...