V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MatthewHan
V2EX  ›  Java

HashMap 无显式赋值容量大小初始化,默认的 16 是在 new 的过程中,还是在第一次 put 的时候?

  •  
  •   MatthewHan · 2020-03-11 18:08:32 +08:00 · 2771 次点击
    这是一个创建于 1478 天前的主题,其中的信息可能已经有所发展或是发生改变。

    《码出高效》中有这么一句话:HashMap 容量并不会在 new 的过程中分配,而是在第一次 put 的时候完成创建。

    文中的源码环境是 JDK11。

    我在本地环境 JDK8 的代码里这样写:

    // 未显式初始化容量大小
    Map<String, String> map = new HashMap<>();
    Class<?> mapClazz = map.getClass();
    Method capacity = mapClazz.getDeclaredMethod("capacity");
    capacity.setAccessible(true);
    System.out.println("不显式的初始化,容量大小为:" + capacity.invoke(map));
    

    输出的结果为:不显式的初始化,容量大小为:16。

    我以为会和ArrayList一样,未显式初始化,容量大小是 0,只有调用一次 add 方法后,才会扩容成默认值的容量大小。

    那《码出高效》这句话该怎么解释好呢?是 JDK 版本的问题吗?反射的是capacity()方法。

    17 条回复    2020-03-13 08:36:19 +08:00
    daimazha
        1
    daimazha  
       2020-03-11 18:22:02 +08:00
    这里指的是 table
    az467
        2
    az467  
       2020-03-11 18:26:02 +08:00
    public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    Lonely
        3
    Lonely  
       2020-03-11 18:26:43 +08:00 via iPhone
    那就话应该是说内部的 node 数组是在第一次 put 的时候初始化。这种问题,你点进去源码看两眼不就很清楚了。
    hhhsuan
        4
    hhhsuan  
       2020-03-11 18:29:00 +08:00 via Android
    容量大小是 16,但空间未必已经分配了,这是两码事
    asche910
        5
    asche910  
       2020-03-11 19:23:04 +08:00
    源码 put 的时候,如果未初始化,会首先调用一次 resize 方法
    coer
        6
    coer  
       2020-03-12 01:48:55 +08:00 via Android
    打断点看构造函数,这样怎么看的出来啊
    sumulige
        7
    sumulige  
       2020-03-12 03:58:20 +08:00 via iPhone
    @hhhsuan new 的过程不是初始化 初始化不就是分配了内存空间 还是起初哪个 16 只是个值并没有完成实例化
    afpro
        8
    afpro  
       2020-03-12 09:59:06 +08:00
    ```
    public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
    throw new IllegalArgumentException("Illegal initial capacity: " +
    initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
    initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
    throw new IllegalArgumentException("Illegal load factor: " +
    loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
    }
    ```

    显然只初始化了一个 float 和一个 int

    ```
    public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
    }

    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;
    ...
    }
    ```

    put 的时候如果 (tab = table) == null 会去 resize

    ```
    final Node<K,V>[] resize() {
    ...
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    ...

    ```

    在 resize 里面 new 了新的 table
    afpro
        9
    afpro  
       2020-03-12 09:59:38 +08:00
    emmm 我尝试用 ``` 放代码 但是没成功 这个怎么样才能正确的贴代码?
    MatthewHan
        10
    MatthewHan  
    OP
       2020-03-12 10:08:54 +08:00
    @afpro #8 我的代码里不是没 put 吗
    afpro
        11
    afpro  
       2020-03-12 10:13:19 +08:00
    @MatthewHan 你是没 put 啊 所以只初始化了两个参数 没有 new table 你的疑惑在哪呢??
    afpro
        12
    afpro  
       2020-03-12 10:14:37 +08:00
    @MatthewHan 你疑惑的是 capacity 这个方法??

    final int capacity() {
    return (table != null) ? table.length :
    (threshold > 0) ? threshold :
    DEFAULT_INITIAL_CAPACITY;
    }

    这不是显然 table 为 null 的时候返回了 threshold 或者 DEFAULT_INITIAL_CAPACITY

    有发帖和跟帖的时间瞄一眼 code 可好。。
    MatthewHan
        13
    MatthewHan  
    OP
       2020-03-12 10:21:50 +08:00
    @afpro #12 所以我不是问这句话怎么理解比较好🐴,不是说我没看源码。。。
    hhhsuan
        14
    hhhsuan  
       2020-03-12 10:35:03 +08:00
    @sumulige #7 没有分配那 16 个空位,直到第一次存数据的的时候才会。
    Angzk3348
        15
    Angzk3348  
       2020-03-12 12:18:14 +08:00
    先来看看
    new HashMap<>();
    只是初始化了 loadFactor 的值.
    ====源码====
    public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    ====源码====


    再来看看
    capacity()
    因为调用的是 无参构造器.
    table 肯定是 null 第一个三目 是 false.
    threshold 没赋值 第二个三目 也是 false.
    ====源码====
    final int capacity() {
    return (table != null) ? table.length : (threshold > 0) ? threshold :DEFAULT_INITIAL_CAPACITY;
    }
    ====源码====

    那么 再来看看
    DEFAULT_INITIAL_CAPACITY
    ====源码====
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    ====源码====
    Aresxue
        16
    Aresxue  
       2020-03-12 16:40:19 +08:00
    默认在 class 中,一直都是 16(常量),你 new 的时候只是建立了引用,put 的时候才会真正分配内存空间
    ShellMings
        17
    ShellMings  
       2020-03-13 08:36:19 +08:00 via iPhone
    码出高效 看看就好 呵呵 😄
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   970 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 21:54 · PVG 05:54 · LAX 14:54 · JFK 17:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.