Java源码解析之HashMap

一、HashMap类声明:

HashMap继承于AbstractMap并且实现了接口Map,Cloneable,Serializable。

public class HashMap<K,V>
    extends AbstractMap<K,V>
     implements Map<K,V>, Cloneable, Serializable
{}

二、HashMap类层次:

HashMap实现了三个接口,继承一个抽象类。除此之外我们应该知道Object是所有类的超类。之所以有一个AbstractMap抽象类,是为了提供一个Map接口实现的骨架,并提供一些实现,最少化实现Map的实现类。

关于序列化,克隆接口另写文章介绍,敬请期待。

三、HashMap存储结构图:

图片来自:http://github.thinkingbar.com/hashmap-analysis/, 敬谢。

四、HashMap变量说明:

//默认的初始化容量,必须是2的的次方数,默认是16;在初始化HashMap没有指定容量时使用
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

//最大容量2^30,用于table数组,下面做的说明大家不要和size混淆
//我们知道int是32位的,最大正整数是2^31-1,
//另外我们分析源码会发现在resize的时候将阈值设为了Integer.MAX_VALUE,即2^31-1
//所以HashMap实际用到的最大size为2^31-1
static final int MAXIMUM_CAPACITY = 1 << 30;

//默认的装载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//空数组,被所有HashMap共享,一般只是作为table的默认值
static final Entry<?,?>[] EMPTY_TABLE = {};

//用于存储HashMap中的桶,长度总是2的次方数,会在第一次put的时候分配内存
//transient表明序列化的时候table不会序列化
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

//size表示HashMap中存储的映射个数
transient int size;

//HashMap实际使用的阈值变量
//一开始这个值是默认的阈值,
//但是当分配table内存是,值为容量*装载因子,即(capacity * load factor)
int threshold;

//装载因子,用以表示table装满程度
//当没有指明装载因子时使用DEFAULT_LOAD_FACTOR
final float loadFactor;

//用以记录HashMap自创建以来结构发生变化的次数
//结构发生变化指:1.增加映射, 2.移除映射, 3.rehash
//这个值会使由这个HashMap得到的迭代器(iterators)快速失败(fail-fast)
//因为在生成迭代器的时候复制了一份modCount当时的值,如果在这之后HashMap发生了结构变化,
//那么这个迭代器中的值就不等于modCount,迭代器就抛出ConcurrentModificationException
//但是通过这个迭代器去改变HashMap的结构是可以的
transient int modCount;

//可选哈希阈值默认值2^31-1
//目的是为了减少String键值的弱哈希计算的冲突
//JVM首先读取系统属性jdk.map.althashing.threshold,
//值为-1时,禁用该值;值为小于0时,抛出异常;值为正则启用可选哈希
//源码分析时会进一步讲解
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

//哈希种子,在分配table存储时会计算该值
transient int hashSeed = 0;

//存储映射的Set变量
private transient Set<Map.Entry<K,V>> entrySet = null;

五、HashMap方法解析:

1、构造方法:

/**
 * 通过容量和装载因子初始化HashMap
 */
public HashMap(int initialCapacity, float loadFactor) {
        //以下对容量值进行检查,确保在(0, MAXIMUM_CAPACITY]之间
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //以下对装载因子进行检查,要求是正的Float数字
        //思考装载因子并没有限制在小于1的范围内,可见可以是大于1的数字,只是这个时候再也反映不出
        //table的装满程度,同时put的冲突几率将增高,装载因子失去设计时的意义
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity; //初始阈值为容量值
        /**
         * HashMap中此init方法的方法体是空的,子类有需要可以实现,
         * 主要是为了执行子类的钩子,用的是模板方法设计模式
         * 这个方法总是在构造方法的最后一步执行,伪构造方法(clone,readObject)也会调用执行
         */
        init(); /
}

/**
 * 使用容量和默认的装载因子初始化HashMap
 */
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

/**
 * 使用默认的容量和装载因子初始化HashMap
 */
public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

2、内部类:

/**
 * 调用处是在虚拟机启动好之后
 */
private static class Holder {
        static final int ALTERNATIVE_HASHING_THRESHOLD;

        static {
            //获得系统属性jdk.map.althashing.threshold
            //此种方式调用时不进行安全检查,doPrivileged里面的代码享有特权
            String altThreshold = java.security.AccessController.doPrivileged(
                new sun.security.action.GetPropertyAction(
                    "jdk.map.althashing.threshold"));

            int threshold;
            try {
                //当系统属性值有被设置,那么获得该值,否者使用可选哈希阈值默认值
                threshold = (null != altThreshold)
                        ? Integer.parseInt(altThreshold)
                        : ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;

                //如果系统属性设置为-1,则赋予Integer最大正整数
                if (threshold == -1) {
                    threshold = Integer.MAX_VALUE;
                }

                //小于0,抛出异常
                if (threshold < 0) {
                    throw new IllegalArgumentException("value must be positive integer.");
                }
            } catch(IllegalArgumentException failed) {
                throw new Error("Illegal value for ‘jdk.map.althashing.threshold‘", failed);
            }

           //最后存储为可选哈希值
            ALTERNATIVE_HASHING_THRESHOLD = threshold;
        }
}

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key; //键值,注意这里key是final的,说明一旦赋值不允许修改,强调key值设计原则
        V value; //映射值
        Entry<K,V> next; //关联的下一个Enrty引用
        int hash; //哈希码

        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }

        /**
         * 此方法被调用,当已存在的Entry中的value被修改的时候
         * 子类需要实现
         */
        void recordAccess(HashMap<K,V> m) {
        }

        /**
         * 此方法被调用,当这个Entry被移除
         * 子类需要实现
         */
        void recordRemoval(HashMap<K,V> m) {
        }
}

/**
 * 迭代器实现抽象类,只有next()未实现,留待子类实现
 */
private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;        // 下一个entry
        int expectedModCount;   // 用于快速失败机制
        int index;               // 桶的位置索引
        Entry<K,V> current;     // 当前entry

        HashIterator() {
            expectedModCount = modCount; //赋予当时HashMap的modCount值
            if (size > 0) {
                Entry[] t = table;
                //找到table数组中按序不为null的那个桶
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry<K,V> nextEntry() {
            //这句很重要,当生成迭代器后,HashMap结构发生了变化,则迅速失败
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();

            if ((next = e.next) == null) {
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
            current = e;
            return e;
        }

        public void remove() {
            if (current == null)
                throw new IllegalStateException();
           //这句很重要,当生成迭代器后,HashMap结构发生了变化,则迅速失败
           if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            //移除后要更新expectedModCount,否者再次调用会迅速失败
            expectedModCount = modCount;
        }
}

private final class ValueIterator extends HashIterator<V> {
        public V next() {
            return nextEntry().value;
        }
 }

private final class KeyIterator extends HashIterator<K> {
        public K next() {
            return nextEntry().getKey(); //强调key值设计理念
        }
}

private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
        public Map.Entry<K,V> next() {
            return nextEntry();
        }
}

/**
 * 用于生成key的set集合,所有方法都委托给HashMap的方法
 */
private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
}

/**
 * 用于生成value集合,所有方法都委托给HashMap的方法
 */
private final class Values extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return newValueIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsValue(o);
        }
        public void clear() {
            HashMap.this.clear();
        }
}

/**
 * 用于生成entry的set集合,所有方法实现都委托给HashMap的方法
 */
private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return newEntryIterator();
        }
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<K,V> e = (Map.Entry<K,V>) o;
            Entry<K,V> candidate = getEntry(e.getKey());
            return candidate != null && candidate.equals(e);
        }
        public boolean remove(Object o) {
            return removeMapping(o) != null;
        }
        public int size() {
            return size;
        }
        public void clear() {
            HashMap.this.clear();
        }
}

3、put方法:

/**
 * 将键值key与映射值value组成一个映射存储在HashMap中
 * 允许key为null,因为key唯一,所以最多有一个这样的映射
 * 允许有多个value为null的映射,但是key不同
 */
public V put(K key, V value) {
        //在第一次put的时候会检查到table为空并初始化
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        //如果key为null就调用专用的方法进行put,然后返回
        if (key == null)
            return putForNullKey(value);
        //根据键值key进行哈希计算得到哈希码
        int hash = hash(key);
        //根据哈希码计算应当将该映射放在table的哪个索引链表下
        int i = indexFor(hash, table.length);
        //遍历第i个链表查找是否存在于key相同的entry,存在则替换entry的value值并返回旧的value值
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this); //put替换存在的entry的value值须调用,子类实现的方法
                return oldValue;
            }
        }

        //如果不存在为key的entry,则添加新的entry到HashMap中
        //因为HashMap结构发生变化,则modCount加1
        //返回null
        modCount++;
        addEntry(hash, key, value, i);
        return null;
}

/**
 * put键值key为null的时候调用的方法
 * 直接在table的第0个索引的链表上查找替换或添加
 */
private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
}

/**
 * 在table的第bucketIndex个链表上添加哈希码为hash,键值为key,映射值为value的entry
 */
void addEntry(int hash, K key, V value, int bucketIndex) {
        //首先检查HashMap的size是否已经到达或者大于阈值,并且第bucketIndex个索引的链表不为null
        //那么HashMap需要rehash尝试申请更多table空间,注意是尝试,不一定能分配到的
        //同时我们也能知道如果第bucketIndex个索引的链表为null,即时超过阈值也不会去申请空间
        //注意size指的是HashMap实际的entry数量,threshold是table的装满程度的具体阈值
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length); //尝试请求申请当前table长度的2倍空间
            //重新计算put映射的key的哈希码
            //因为这个方法是给所有key的添加使用的,所以要考虑可以为null的情况
            hash = (null != key) ? hash(key) : 0;
            //重新计算put的映射应该放在申请新空间后的table的哪个索引链表上
            bucketIndex = indexFor(hash, table.length);
        }

        //创建Entry对象,并插入到第bucketIndex个索引链表的第一个位置
        createEntry(hash, key, value, bucketIndex);
}

/**
 * 创建Entry对象,并插入到第bucketIndex个索引链表的第一个位置,size加1
 */
void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
}

4、get方法:

/**
 * 获取HashMap中键值为key的entry的value值
 * 如果返回null,有可能存储的value就是null,也有可能没有key对应entry,需要结合containsKey检查
 */
public V get(Object key) {
        if (key == null)
            return getForNullKey();  //key为null,调用专用方法
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
}

private V getForNullKey() {
        if (size == 0) {
            return null;
        }
        //直接到table的第0个索引链表下查找
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
}

final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        //首先根据key计算哈希码,然后根据哈希码找到table下的索引链表,最后查找
        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
}

5、remove方法:

/**
 * 从HashMap中移除键值为key的entry,并且返回对应的value值
 */
public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
}

final Entry<K,V> removeEntryForKey(Object key) {
        if (size == 0) {
            return null;
        }
        //首先根据key计算哈希码,然后根据哈希码找到table下的索引链表
        int hash = (key == null) ? 0 : hash(key);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

       //查找链表
        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            //找到该key对应的entry
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
               //结构该表,计数加1;同时HashMap大小减一
                modCount++;
                size--;
                //如果移除的是table索引的第一个entry,则直接修改table[i]
                if (prev == e)
                    table[i] = next;
                //不是第一个entry,则将移除entry的前一个entry的next指向移除entry的next
                else
                    prev.next = next;
                //记录移除的entry
                e.recordRemoval(this);
                return e;
            }
            //顺序后移,再次循环
            prev = e;
            e = next;
        }

        //返回移除的entry
        return e;
}

6、一些公用方法:

/**
 * 在HashMap使用之前需要做一次膨化处理
 */
private void inflateTable(int toSize) {
        //找到大于等于toSize且是2的次方数的最小数
        //所以我们需要知道table数组的长度一定是2的次方数
        int capacity = roundUpToPowerOf2(toSize);

        //计算阈值
        //取二者中较小的那个
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        //初始化哈希种子
        initHashSeedAsNeeded(capacity);
}

private static int roundUpToPowerOf2(int number) {
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}

/**
 * 初始化hashSeed,但是貌似hashSeed一直是0,不知道其中缘由
 */
final boolean initHashSeedAsNeeded(int capacity) {
        boolean currentAltHashing = hashSeed != 0;
        boolean useAltHashing = sun.misc.VM.isBooted() &&  //检查虚拟机是否已启动
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)  //生成随机种子
                : 0;
        }
        return switching;
}

final int hash(Object k) {
        int h = hashSeed;
        //当哈希种子不为0且k是String时调用特殊的哈希算法
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        // 下面的方法可以使在table每个位置的冲突次数都是一个常数值,在装载因子为0.75的时候为8
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
}

static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        // 因为table的length总是2的次方数,所以下面的方法是对h在length上做模运算
        // 比如50 % 16 = 2 = 000010 = 110010 & 001111
        return h & (length-1);
}

/**
 * resize table的大小
 */
void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        //如果容量已经到达最大容量值,就将阈值设为Integer最大值,返回
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        //将当前table内容转换到新的容量table中
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

/**
 * 将旧table中的entry转化到新table中去,rehash指明是否需要重新计算哈希码
 */
void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
}

7、其他方法:略

六、说明:

本文对HashMap的源码进行了简略分析。本文基于Java7的JDK1.7.0_79-64分析,由于Java8有改变,之后会基于Java8另写文章解析。
由于作者文笔拙劣,分析粗鄙,纰漏之处还望各位不吝斧正。

时间: 2024-10-10 09:09:40

Java源码解析之HashMap的相关文章

[java源码解析]对HashMap源码的分析(二)

上文我们讲了HashMap那骚骚的逻辑结构,这一篇我们来吹吹它的实现思想,也就是算法层面.有兴趣看下或者回顾上一篇HashMap逻辑层面的,可以看下HashMap源码解析(一).使用了哈希表得"拉链法". 我打算按这个顺序来讲HashMap:几个关键属性 -> 构造方法-> 存取元素方法 ->解决hash冲突方法->HashMap扩容问题. 4个关键属性: /** *HashMap的存储大小 */ transient int size; /** * HashMa

【Java源码解析】-- HashMap源码解析

目录 源码解析 1.构造方法 无参构造方法 int型参数的构造方法 int,float两个参数的构造方法 hsah方法 2.添加元素(put()方法) 3.扩容方法(resize()方法) 4.获取元素(get()方法) 5.移除元素(remove()) 6.树化(treeifyBin()) 关于HashMap常见的问题 1.为什么容量始终是2的幂次? 3.既然红黑树那么好,为啥hashmap不直接采用红黑树,而是当大于等于8个的时候才转换红黑树? 4.JDK1.7 扩容死锁产生原因 5.JDK

深入Java源码解析容器类List、Set、Map

1 常用容器继承关系图先上一张网上的继承关系图个人觉得有些地方不是很准确,比如Iterator不是容器,只是一个操作遍历集合的方法接口,所以不应该放在里面.并且Map不应该继承自Collection.所以自己整理了一个常用继承关系图如下如上图所示,接下去会自顶向下解释重要的接口和实现类.2 Collection和Map在Java容器中一共定义了2种集合, 顶层接口分别是Collection和Map.但是这2个接口都不能直接被实现使用,分别代表两种不同类型的容器.简单来看,Collection代表

Java源码解析——集合框架(二)——ArrayBlockingQueue

ArrayBlockingQueue源码解析 ArrayBlockingQueue是一个阻塞式的队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口.底层以数组的形式保存数据(实际上可看作一个循环数组).常用的操作包括 add ,offer,put,remove,poll,take,peek. 一.类声明 public class ArrayBlockingQueue<E> extends AbstractQueue<E> i

Java源码解析 - ThreadPoolExecutor 线程池

1 线程池的好处 线程使应用能够更加充分合理地协调利用CPU.内存.网络.I/O等系统资源.线程的创建需要开辟虚拟机栈.本地方法栈.程序计数器等线程私有的内存空间;在线程销毁时需要回收这些系统资源.频繁地创建和销毁线程会浪费大量的系统资源,增加并发编程风险. 在服务器负载过大的时候,如何让新的线程等待或者友好地拒绝服务? 这些都是线程自身无法解决的;所以需要通过线程池协调多个线程,并实现类似主次线程隔离.定时执行.周期执行等任务. 线程池的作用包括:●利用线程池管理并复用线程.控制最大并发数等●

Java源码解析|HashMap的前世今生

HashMap的前世今生 Java8在Java7的基础上,做了一些改进和优化. 底层数据结构和实现方法上,HashMap几乎重写了一套 所有的集合都新增了函数式的方法,比如说forEach,也新增了很多好用的函数. 前世--Java 1.7 底层数据结构 数组 + 链表 在Java1.7中HashMap使用数组+链表来作为存储结构 数组就类似一个个桶构成的容器,链表用来解决冲突,当出现冲突时,就找到当前数据应该存储的桶的位置(数组下标),在当前桶中插入新链表结点. 如下图所示: 链表结点中存放(

JDK核心JAVA源码解析(1) - Object

想写这个系列很久了,对自己也是个总结与提高.原来在学JAVA时,那些JAVA入门书籍会告诉你一些规律还有法则,但是用的时候我们一般很难想起来,因为我们用的少并且不知道为什么.知其所以然方能印象深刻并学以致用. 首先我们从所有类的父类Object开始: 1. Object类 (1)hashCode方法和equals方法 public native int hashCode(); public boolean equals(Object obj) { return (this == obj); }

jdk1.8源码解析:HashMap底层数据结构之链表转红黑树的具体时机

前言 本文从三个部分去探究HashMap的链表转红黑树的具体时机: 一.从HashMap中有关“链表转红黑树”阈值的声明: 二.[重点]解析HashMap.put(K key, V value)的源码: 三.测试: 一.从HashMap中有关“链表转红黑树”阈值的声明,简单了解HashMap的链表转红黑树的时机 在 jdk1.8 HashMap底层数据结构:散列表+链表+红黑树(图解+源码)的 “四.问题探究”中,我有稍微提到过散列表后面跟什么数据结构是怎么确定的: HashMap中有关“链表转

Java源码解析|String源码与常用方法

String源码与常用方法 1.栗子 代码: public class JavaStringClass { public static void main(String[] args) { String s ="hello"; s = "world"; //内存地址已经修改 原来地址上的值还是不变的 String s2 = "hello"; //从常量值中找到并引用 String s4 = new String("hello"