调试JDK源代码-一步一步看HashMap怎么Hash和扩容

调试JDK源代码-一步一步看HashMap怎么Hash和扩容

调试JDK源代码-ConcurrentHashMap实现原理

调试JDK源代码-HashSet实现原理

调试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

说明:须要做好备份以防搞错。

參考:eclipse怎样debug调试jdk源代码

初调HashMap,怎样改动JDK的源代码进行调试

编译JDK源码。开启Debug信息

四.调试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,序列化对象的时候。这个属性就不会序列化到指定的目的地中。

Java transientkeyword使用小记

函数体内声明一个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的性能。

时间: 2024-10-11 11:35:53

调试JDK源代码-一步一步看HashMap怎么Hash和扩容的相关文章

在ASP.NET 5项目中使用和调试外部源代码包

(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:由于在ASP.NET 5中,项目依赖都是通过"包"来引用,所以使用和调试外部代码(比如DNX运行时)成为可能. .NET开源带来的一个额外好处就是,让ASP.NET 5的函数库引用变得更加灵活,不仅可以引用来自Nuget的编译好的包,也可以引用本地源代码.本地源代码只要符合打包规则,即源代码根文件夹"src"包含项目子文件夹,项目目录包含project.

调试器第二讲,单步步入/步过功能实现,以及基本的断点功能实现

调试器第二讲,单步步入/步过功能实现,以及基本的断点功能实现 昨天,我们实现了调试器的基本框架,那么今天我们实现单步功能,还有断点功能,以及使用反汇编引擎 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 一丶反汇编引擎的编译,生成LIB 首先,我们有一个反汇编引擎的代码,现在我们编译连接一下(注意,可以使用GitHub下载,或者百度Google下载) 关于反汇编引擎的介绍: 请参考转载博客 http://blog.c

C#WPF 语音开发教程 源代码下载 csdn tts(text to sound) 一步一步 教你制作语音软件 附图和源代码

C#WPF  语音开发教程  一步一步 教你制作语音软件 附图和源代码 效果展示 一 项目准备 1.vs2012开发平台 2.微软的语音软件库 下载:http://download.csdn.net/detail/wyx100/8431269 (含实例项目源代码) 二.开发目标 制作一个语音软件,可以朗读文字: 多个语音库:男音和女音.支持英文和中文朗读: 支持选择播放设备 支持朗读语速选择 支持音量选择 三 开发过程 1.新建WpfSpeechDemo工程 文件(vs开发平台左上角)----新

Rhythmk 一步一步学 JAVA (21) JAVA 多线程

1.JAVA多线程简单示例 1.1 .Thread  集成接口 Runnable 1.2 .线程状态,可以通过  Thread.getState()获取线程状态: New (新创建) Runnable (可以运行) Blocked  (被阻塞) Waiting  (等待) Timed waiting (计时等待) Terminated  (被终止) ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

【转】朱兆祺带你一步一步学习嵌入式(连载)

原文网址:http://bbs.elecfans.com/jishu_357014_2_1.html#comment_top  从最初涉及嵌入式Linux开始到现在,深深的知道嵌入式的每一步学习都是举步维艰.从去年11月份开始,我就着手整理各种学习资料,希望推动嵌入式学习的前进贡献自己微不足道的一份力量.从去年到现在,将C语言的学习经验整理成<攻破C语言笔试与机试陷阱及难点>(现在仍在更新),这份资料已经在电子发烧友论坛的单片机论坛连载(http://bbs.elecfans.com/jish

在使用Reference Source调试.Net 源代码时如何取消optimizations(代码优化)-翻译

在使用PDB调试XAF时,发现好多变量都看不到.都被优化掉了. 下面的方法可以解决. 当你在使用Reference Source functionality in VS 2008 调试.Net 的源代码的时候,你会发现很多变量没法再调试时查看. 这是因为源代码服务器上提供的代码默认是为最终销售优化过的(optimized ).这些值虽然你没法查看,但不会阻断单步执行,大部分情况下你可能不需要查看. 但如果你真的需要查看,这里还是有一个办法的. 你需要靠诉CLR不要加载pre-JIT(也加NGEN

通过分析 JDK 源代码研究 TreeMap 红黑树算法实现

TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点. TreeSet 和 TreeMap 的关系 为了让大家了解 TreeMap 和 TreeSet 之间的关系,下面先看 TreeSet 类的部分源代码: public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializab

一步一步跟我学DeviceOne开发 - 仿微信应用(一,二,三)

这是一个系列的文档,长期目标是利用DeviceOne开发一些目前使用广泛的优质手机应用,我们会最大化的实现这些应用的每一个功能和细节,不只停留在简单的UI模仿和Demo阶段,而是一个基本可以使用的实际App. 在实现的过程中,会有很多困难,还会发现有一些功能目前缺乏组件支持而无法实现,也会碰见各种移动开发中都会碰到的常见技术问题.一步一步的操作和问题的解决可以让开发者直观的了解通过DeviceOne如何开发一个实际App,也可以了解移动开发本身的很多技术细节,可以让App开发者少走很多弯路. 这

调试 Hadoop 源代码

环境是 64bit Ubuntu 14.04 系统, jdk 1.7 以及 Eclipse Mars (4.5) 这里介绍两种调试 Hadoop 源代码的方法: 利用 Eclipse 远程调试工具和打印调试日志. 这两种方法均可以调试伪分布式工作模式和完全分布式工作模式下的 Hadoop. (1) 利用 Eclipse 进行远程调试 下面以调试 ResourceManager 为例, 介绍利用 Eclipse 远程调试的基本方法, 这可分两步进行. 步骤 1  调试模式下启动 Hadoop. 在