浅析 java 中的 Map.of(...) 方法和 Map.ofEntries(...) 方法
JDK 9 在 java.util.Map 接口中提供了一组 of(...) 静态方法(这里我用 ... 表示任意的参数类型/个数,包括 <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 0 </math>0 个参数的情况)以及一个 ofEntries(...) 方法。通过调用这些静态方法,我们可以构造不可变的(immutable) map。
这组 Map.of(...) 方法的参数中,key-value pair 的数量 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 可以是 <math xmlns="http://www.w3.org/1998/Math/MathML"> { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } \{0,1,2,3,4,5,6,7,8,9,10\} </math>{0,1,2,3,4,5,6,7,8,9,10} 中的任意值。当我们需要提供超过 <math xmlns="http://www.w3.org/1998/Math/MathML"> 10 10 </math>10 组 key-value pair 时,可以调用 Map.ofEntries(...) 方法。在 这个方法 的 javadoc 里可以找到如下的例子 ⬇️

我把代码复制到下方了 ⬇️
            
            
              java
              
              
            
          
          import static java.util.Map.entry;
     Map<Integer,String> map = Map.ofEntries(
         entry(1, "a"),
         entry(2, "b"),
         entry(3, "c"),
         ...
         entry(26, "z"));
        当您调用这些方法时,是否想过以下问题? ⬇️
Map.of(...)/Map.ofEntries(...)返回的Map的精确类型是什么?是HashMap吗,还是其他类型的Map?- 为什么 
Map.of(...)/Map.ofEntries(...)返回的Map具有不可变(immutable)的特点,它是如何实现的? Map.of(...)/Map.ofEntries(...)所返回的Map,是如何存储元素的?
本文会从以上 <math xmlns="http://www.w3.org/1998/Math/MathML"> 3 3 </math>3 个问题入手,对 Map.of(...)/Map.ofEntries(...) 方法进行探索。
要点
- 
Map.of(...)所返回的Map的精确类型与参数中key-valuepair的 个数 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 有关- 如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n=1 </math>n=1 (即 
key-valuepair的数量为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1),Map的精确类型会是java.util.ImmutableCollections$Map1 - 如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> n ≤ 10 n \le 10 </math>n≤10 且 <math xmlns="http://www.w3.org/1998/Math/MathML"> n ≠ 1 n\ne 1 </math>n=1 (即 
key-valuepair的数量不超过 <math xmlns="http://www.w3.org/1998/Math/MathML"> 10 10 </math>10 且不是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1) ,Map的精确类型会是java.util.ImmutableCollections$MapN 
 - 如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n=1 </math>n=1 (即 
 - 
Map.ofEntries(...)所返回的Map的精确类型与参数中Entry的 数量 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 有关- 如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n=1 </math>n=1 (即 
Entry的数量为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1),Map的精确类型会是java.util.ImmutableCollections$Map1 - 如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> n ≠ 1 n \ne 1 </math>n=1 (即 
Entry的数量不是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1),Map的精确类型会是java.util.ImmutableCollections$MapN 
 - 如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n=1 </math>n=1 (即 
 - 
AbstractImmutableMap中的put(...)/remove(...)等方法会抛出UnsupportedOperationException,而Map1和MapN都继承了AbstractImmutableMap,所以Map1/MapN的实例就是不可变(immutable)的了 - 
MapN中有table字段,它的length会是key-valuepair的数量的 <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 4 </math>4 倍。在保存元素和查询元素时,会使用 开放寻址法(open addressing) 
我画了简单的思维导图 ⬇️
正文
问题一: Map.of(...)/Map.ofEntries(...) 返回的 Map 的精确类型是什么?是 HashMap 吗,还是其他类型的 Map?
我写了如下的代码来进行探索 ⬇️
            
            
              java
              
              
            
          
          import java.util.Map;
public class Question1 {
    public static void main(String[] args) {
        // for Map.of(...)
        System.out.println(Map.of().getClass().getName());
        System.out.println(Map.of('A', 65).getClass().getName());
        System.out.println(Map.of('A', 65, 'B', "66").getClass().getName());
        
        // for Map.ofEntries(...)
        System.out.println(Map.ofEntries().getClass().getName());
        System.out.println(
            Map.ofEntries(
                Map.entry('A', 65)
            ).getClass().getName()
        );
        System.out.println(
            Map.ofEntries(
                Map.entry('A', 65),
                Map.entry('B', 66)
            ).getClass().getName()
        );
    }
}
        请将以上代码保存为 Question1.java,用如下的命令可以编译 Question1.java 并运行 Question1 类的 main(...) 方法。
            
            
              bash
              
              
            
          
          javac Question1.java
java Question1
        运行结果如下
            
            
              text
              
              
            
          
          java.util.ImmutableCollections$MapN
java.util.ImmutableCollections$Map1
java.util.ImmutableCollections$MapN
java.util.ImmutableCollections$MapN
java.util.ImmutableCollections$Map1
java.util.ImmutableCollections$MapN
        看来 Map.of(...)/Map.ofEntries(...) 方法返回的值可以是以下两个类的实例 ⬇️
java.util.ImmutableCollections$Map1java.util.ImmutableCollections$MapN
但仅凭以上几行代码所列举的情况,并不能说明 Map.of(...)/Map.ofEntries(...) 的返回值是否有可能是其他类的实例,我们去看看源码。
Map.of(...) 方法
在 Map.java 中可以找到各个 Map.of(...) 方法的代码。
key-value pair 的个数 <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 n=0,1,2,3,4,5,6,7,8,9,10 </math>n=0,1,2,3,4,5,6,7,8,9,10 共有 <math xmlns="http://www.w3.org/1998/Math/MathML"> 11 11 </math>11 种情况。其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 n=2,3,4,5,6,7,8,9,10 </math>n=2,3,4,5,6,7,8,9,10 的情况代码高度雷同,以下只展示 <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 0 , 1 , 2 , 3 n=0,1,2,3 </math>n=0,1,2,3 的情况 ⬇️ (为了节约空间,对应的 javadoc 都略去了)
            
            
              java
              
              
            
          
              
    @SuppressWarnings("unchecked")
    static <K, V> Map<K, V> of() {
        return (Map<K,V>) ImmutableCollections.EMPTY_MAP;
    }
    
    static <K, V> Map<K, V> of(K k1, V v1) {
        return new ImmutableCollections.Map1<>(k1, v1);
    }
    static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2) {
        return new ImmutableCollections.MapN<>(k1, v1, k2, v2);
    }
    static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
        return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3);
    }
        可以将代码中的信息汇总成下方的表格 ⬇️
key-value pair 的个数 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n | 
Map.of(...) 返回什么 | 
说明 | 
|---|---|---|
| <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 0 n=0 </math>n=0 | ImmutableCollections.EMPTY_MAP | 
⬅️ 它是 ImmutableCollections.MapN 类型的 | 
| <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n=1 </math>n=1 | ImmutableCollections.Map1 的实例 | 
|
| <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 ≤ n ≤ 10 2\le n \le 10 </math>2≤n≤10 | ImmutableCollections.MapN 的实例 | 
<math xmlns="http://www.w3.org/1998/Math/MathML"> n = 0 n=0 </math>n=0 时,Map.of() 返回的也是 java.util.ImmutableCollections$MapN 的实例。所以可以把上方的表格再简化一下,变成下面这样 ⬇️
key-value pair 的个数 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n | 
Map.of(...) 返回什么 | 
|---|---|
| <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n=1 </math>n=1 | java.util.ImmutableCollections$Map1 的实例 | 
| <math xmlns="http://www.w3.org/1998/Math/MathML"> n ≤ 10 n \le 10 </math>n≤10 且 <math xmlns="http://www.w3.org/1998/Math/MathML"> n ≠ 1 n\ne 1 </math>n=1 | java.util.ImmutableCollections$MapN 的实例 | 
Map.ofEntries(...) 方法
我们直接看 Map.ofEntries(...) 的源码 ⬇️
            
            
              java
              
              
            
          
              @SafeVarargs
    static <K, V> Map<K, V> ofEntries(Entry<? extends K, ? extends V>... entries) {
        if (entries.length == 0) { // implicit null check of entries array
            @SuppressWarnings("unchecked")
            var map = (Map<K,V>) ImmutableCollections.EMPTY_MAP;
            return map;
        } else if (entries.length == 1) {
            // implicit null check of the array slot
            return new ImmutableCollections.Map1<>(entries[0].getKey(),
                    entries[0].getValue());
        } else {
            Object[] kva = new Object[entries.length << 1];
            int a = 0;
            for (Entry<? extends K, ? extends V> entry : entries) {
                // implicit null checks of each array slot
                kva[a++] = entry.getKey();
                kva[a++] = entry.getValue();
            }
            return new ImmutableCollections.MapN<>(kva);
        }
    }
        这个方法内用 if-else 语句对 entries.length 进行了分类处理。具体的情况,我整理成了如下表格 ⬇️
entries.length 的值 | 
Map.ofEntries(...) 返回什么 | 
说明 | 
|---|---|---|
| <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 0 </math>0 | ImmutableCollections.EMPTY_MAP | 
⬅️ 它是 ImmutableCollections.MapN 类型的 | 
| <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1 | ImmutableCollections.Map1 的实例 | 
|
| <math xmlns="http://www.w3.org/1998/Math/MathML"> ≥ 2 \ge 2 </math>≥2 | ImmutableCollections.MapN 的实例 | 
<math xmlns="http://www.w3.org/1998/Math/MathML"> e n t r i e s . l e n g t h = 0 entries.length=0 </math>entries.length=0 时,Map.ofEntries(...) 返回的也是 java.util.ImmutableCollections$MapN 的实例。所以可以把上方的表格再简化一下,变成下面这样 ⬇️
entries.length 的值 | 
Map.ofEntries(...) 返回什么 | 
|---|---|
| <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1 | ImmutableCollections.Map1 的实例 | 
| <math xmlns="http://www.w3.org/1998/Math/MathML"> ≠ 1 \ne 1 </math>=1 | ImmutableCollections.MapN 的实例 | 
Map1/MapN 的类图如下(图中把所有的泛型信息都省略了) ⬇️
| 在上图中的类名/接口名 | Fully Qualified Name | 
|---|---|
AbstractImmutableMap | 
java.util.ImmutableCollections$AbstractImmutableMap | 
AbstractMap | 
java.util.AbstractMap | 
Map | 
java.util.Map | 
Map1 | 
java.util.ImmutableCollections$Map1 | 
MapN | 
java.util.ImmutableCollections$MapN | 
Serializable | 
java.io.Serializable | 
小结
Map.of(...) 所返回的 Map 的精确类型与参数中 key-value pair 的 个数 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 有关
- 如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n=1 </math>n=1,
Map的精确类型会是java.util.ImmutableCollections$Map1 - 如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> n ≤ 10 n \le 10 </math>n≤10 且 <math xmlns="http://www.w3.org/1998/Math/MathML"> n ≠ 1 n\ne 1 </math>n=1,
Map的精确类型会是java.util.ImmutableCollections$MapN 
Map.ofEntries(...) 所返回的 Map 的精确类型与参数中 Entry 的 数量 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 有关
- 如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n=1 </math>n=1,
Map的精确类型会是java.util.ImmutableCollections$Map1 - 如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> n ≠ 1 n \ne 1 </math>n=1,
Map的精确类型会是java.util.ImmutableCollections$MapN 
问题二:为什么 Map.of(...)/Map.ofEntries(...) 返回的 Map 具有不可变(immutable)的特点,它是如何实现的?
我们写些代码来进行分析。 请将以下代码保存为 Question2.java。
            
            
              java
              
              
            
          
          import java.util.Map;
public class Question2 {
    public static void main(String[] args) {
        Map.of().put('a', 97);
    }
}
        如下的命令可以编译 Question2.java 以及运行 Question2 类中的 main(...) 方法。
            
            
              bash
              
              
            
          
          javac Question2.java
java Question2
        运行上述命令后,会看到以下的报错
            
            
              text
              
              
            
          
          Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:159)
	at java.base/java.util.ImmutableCollections$AbstractImmutableMap.put(ImmutableCollections.java:1318)
	at Question2.main(Question2.java:5)
        根据上述报错信息,可以找到抛出异常的位置如下 ⬇️


从图中的代码可以看出 clear()/putAll(...)/remove(...) 等方法都会抛出 UnsupportedOperationException
我画了类图来辅助理解 ⬇️ (图中将所有泛型信息都省略了,和 问题二 无关的接口/字段/方法也略去了)
小结
AbstractImmutableMap 中的 clear()/putAll(...)/remove(...) 等方法会抛出 UnsupportedOperationException,而 Map1 和 MapN 都继承了 AbstractImmutableMap,所以 Map1/MapN 的实例是不可变的(immutable)。
问题三:Map.of(...)/Map.ofEntries(...) 所返回的 Map,是如何存储元素的?
这里需要区分 Map1/MapN 两种情况
Map1
在 ImmutableCollections.java 中,可以找到 Map1 的代码,其中部分代码如下 ⬇️
            
            
              java
              
              
            
          
              // Not a jdk.internal.ValueBased class; disqualified by fields in superclass AbstractMap
    static final class Map1<K,V> extends AbstractImmutableMap<K,V> implements Serializable {
        @Stable
        private final K k0;
        @Stable
        private final V v0;
        Map1(K k0, V v0) {
            this.k0 = Objects.requireNonNull(k0);
            this.v0 = Objects.requireNonNull(v0);
        }
        @Override
        public Set<Map.Entry<K,V>> entrySet() {
            return Set.of(new KeyValueHolder<>(k0, v0));
        }
        @Override
        public V get(Object o) {
            return o.equals(k0) ? v0 : null; // implicit nullcheck of o
        }
        @Override
        public boolean containsKey(Object o) {
            return o.equals(k0); // implicit nullcheck of o
        }
        @Override
        public boolean containsValue(Object o) {
            return o.equals(v0); // implicit nullcheck of o
        }
        @Override
        public int size() {
            return 1;
        }
        @Override
        public boolean isEmpty() {
            return false;
        }
        Map1 的字段
Map1 中有如下两个字段 ⬇️
private final K k0private final V v0
Map1 的构造函数
在调用 Map1(K k0, V v0) 这个构造函数时,
k0参数会被赋给k0字段v0参数会被赋给v0字段
Map1 中的 get(Object)/containsKey(Object)/size()/isEmpty() 等方法的逻辑比较直观,这里就不赘述了。
小结
Map1 可以处理 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1 个 key-value pair 的情况。
MapN
在 ImmutableCollections.java 中,可以找到 MapN 的代码,其中部分代码如下 ⬇️
            
            
              java
              
              
            
          
              /**
     * An array-based Map implementation. There is a single array "table" that
     * contains keys and values interleaved: table[0] is kA, table[1] is vA,
     * table[2] is kB, table[3] is vB, etc. The table size must be even. It must
     * also be strictly larger than the size (the number of key-value pairs contained
     * in the map) so that at least one null key is always present.
     * @param <K> the key type
     * @param <V> the value type
     */
    // Not a jdk.internal.ValueBased class; disqualified by fields in superclass AbstractMap
    static final class MapN<K,V> extends AbstractImmutableMap<K,V> implements Serializable {
        @Stable
        final Object[] table; // pairs of key, value
        @Stable
        final int size; // number of pairs
        MapN(Object... input) {
            if ((input.length & 1) != 0) { // implicit nullcheck of input
                throw new InternalError("length is odd");
            }
            size = input.length >> 1;
            int len = EXPAND_FACTOR * input.length;
            len = (len + 1) & ~1; // ensure table is even length
            table = new Object[len];
            for (int i = 0; i < input.length; i += 2) {
                @SuppressWarnings("unchecked")
                    K k = Objects.requireNonNull((K)input[i]);
                @SuppressWarnings("unchecked")
                    V v = Objects.requireNonNull((V)input[i+1]);
                int idx = probe(k);
                if (idx >= 0) {
                    throw new IllegalArgumentException("duplicate key: " + k);
                } else {
                    int dest = -(idx + 1);
                    table[dest] = k;
                    table[dest+1] = v;
                }
            }
        }
        @Override
        public boolean containsKey(Object o) {
            Objects.requireNonNull(o);
            return size > 0 && probe(o) >= 0;
        }
        @Override
        public boolean containsValue(Object o) {
            Objects.requireNonNull(o);
            for (int i = 1; i < table.length; i += 2) {
                Object v = table[i];
                if (v != null && o.equals(v)) {
                    return true;
                }
            }
            return false;
        }
        @Override
        public int hashCode() {
            int hash = 0;
            for (int i = 0; i < table.length; i += 2) {
                Object k = table[i];
                if (k != null) {
                    hash += k.hashCode() ^ table[i + 1].hashCode();
                }
            }
            return hash;
        }
        @Override
        @SuppressWarnings("unchecked")
        public V get(Object o) {
            if (size == 0) {
                Objects.requireNonNull(o);
                return null;
            }
            int i = probe(o);
            if (i >= 0) {
                return (V)table[i+1];
            } else {
                return null;
            }
        }
        @Override
        public int size() {
            return size;
        }
        @Override
        public boolean isEmpty() {
            return size == 0;
        }
        在上面的代码中,可以看到 MapN 中的字段以及构造函数的逻辑。
MapN 的字段
MapN 中有如下两个字段 ⬇️
final Object[] tablefinal int size
而这两个字段的作用是 ⬇️
- 在 
MapN的构造函数中,会向table字段填充元素(具体是如何填充的,我们等一下再说),而table字段是保存各个key-valuepair的地方。 size字段用于保存key-valuepair的数量。
MapN 的构造函数
我在构造函数的代码中加了点注释 ⬇️ (代码里原有的注释也没删除)
            
            
              java
              
              
            
          
              MapN(Object... input) {
        if ((input.length & 1) != 0) { // implicit nullcheck of input
            throw new InternalError("length is odd");
        }
        // size 是 key-value pair 的数量
        size = input.length >> 1;
        // EXPAND_FACTOR 值为 2,所以 table 的长度会是 key-value pair 的数量的 4 倍
        int len = EXPAND_FACTOR * input.length;
        len = (len + 1) & ~1; // ensure table is even length
        table = new Object[len];
        
        // 遍历处理,为每一个 key-value pair 找到保存它的地方
        for (int i = 0; i < input.length; i += 2) {
            @SuppressWarnings("unchecked")
                K k = Objects.requireNonNull((K)input[i]);
            @SuppressWarnings("unchecked")
                V v = Objects.requireNonNull((V)input[i+1]);
            // 利用开放寻址法(open addressing)找到 k 对应的位置
            int idx = probe(k);
            if (idx >= 0) {
                throw new IllegalArgumentException("duplicate key: " + k);
            } else {
                // -(idx + 1) 就是 k 应该去的位置
                int dest = -(idx + 1);
                table[dest] = k;
                table[dest+1] = v;
            }
        }
    }
        这里大部分逻辑都很直观,只有 int idx = probe(k); 这一行看起来不太好懂。 这里涉及 开放寻址法(open addressing) 。我在 [Java] 浅析 Set.of(...) 方法 一文中对 开放寻址法 进行了介绍,有兴趣的读者可以看一看,本文就不重复介绍它了。我在 probe(Object) 方法里也加了些注释 ⬇️ (代码里原有的注释也没删除)
            
            
              java
              
              
            
          
          // returns index at which the probe key is present; or if absent,
// (-i - 1) where i is location where element should be inserted.
// Callers are relying on this method to perform an implicit nullcheck
// of pk.
private int probe(Object pk) {
    // key (即 pk) 对应的下标总是偶数
    int idx = Math.floorMod(pk.hashCode(), table.length >> 1) << 1;
    while (true) {
        @SuppressWarnings("unchecked")
        K ek = (K)table[idx];
        if (ek == null) { // 说明 idx 这个下标尚未被占用
            return -idx - 1;
        } else if (pk.equals(ek)) { // 说明有重复的 key 出现
            return idx;
        } else if ((idx += 2) == table.length) { // idx 这个下标被其他 key 占据,按照开放寻址法的思路,应该继续检查 (id + 2) 的位置(注意 table 被视为循环数组)
            idx = 0;
        }
    }
}
        下面举个例子来说明 MapN 里是如何使用 开放寻址法 来保存 key-value pair 的。
            
            
              java
              
              
            
          
          import java.lang.reflect.Field;
import java.util.Map;
public class Question3 {
    public static void main(String[] args) throws Exception {
        // 随便找个对应的汉字(g->gao 高, w->wen 温, h->hai 海, d->dao 岛)
        Map<Character, String> map = Map.of(
            'g', "高", 
            'w', "温", 
            'h', "海", 
            'd', "岛"
        );
        Field field = map.getClass().getDeclaredField("table");
        field.setAccessible(true);
        Object[] table = (Object[]) field.get(map);
        for (int i = 0; i < table.length; i++) {
            if (table[i] != null) {
                String message = String.format("[%s](%s) is at index: %s", table[i], table[i].getClass().getSimpleName(), i);
                System.out.println(message);
            } else {
                System.out.println("// null is at index: " + i);
            }
        }
    }
}
        请将上方的代码保存为 Question3.java。用下方的命令可以编译 Question3.java 并运行 Question3 里的 main(...) 方法。
            
            
              bash
              
              
            
          
          javac Question3.java
java --add-opens=java.base/java.util=ALL-UNNAMED Question3
        运行结果如下
            
            
              text
              
              
            
          
          [w](Character) is at index: 0
[温](String) is at index: 1
[h](Character) is at index: 2
[海](String) is at index: 3
// null is at index: 4
// null is at index: 5
// null is at index: 6
// null is at index: 7
[d](Character) is at index: 8
[岛](String) is at index: 9
// null is at index: 10
// null is at index: 11
// null is at index: 12
// null is at index: 13
[g](Character) is at index: 14
[高](String) is at index: 15
        | <math xmlns="http://www.w3.org/1998/Math/MathML"> k e y → v a l u e key \to value </math>key→value | hashCode of key | 
hashCode <math xmlns="http://www.w3.org/1998/Math/MathML"> ( m o d 8 ) \pmod 8 </math>(mod8) | 
idx 的初始值 | table 的内容 | 
|---|---|---|---|---|
'g' <math xmlns="http://www.w3.org/1998/Math/MathML"> → \to </math>→ "高" | 
<math xmlns="http://www.w3.org/1998/Math/MathML"> 103 103 </math>103 ( <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 x 67 0x67 </math>0x67) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 103 ≡ 7 ( m o d 8 ) 103\equiv 7\pmod 8 </math>103≡7(mod8) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 7 × 2 = 14 7\times 2 = 14 </math>7×2=14 | ![]()  | 
'w' <math xmlns="http://www.w3.org/1998/Math/MathML"> → \to </math>→ "温" | 
<math xmlns="http://www.w3.org/1998/Math/MathML"> 119 119 </math>119 ( <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 x 77 0x77 </math>0x77) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 119 ≡ 7 ( m o d 8 ) 119\equiv 7\pmod 8 </math>119≡7(mod8) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 7 × 2 = 14 7\times 2 = 14 </math>7×2=14 | ![]()  | 
'h' <math xmlns="http://www.w3.org/1998/Math/MathML"> → \to </math>→ "海" | 
<math xmlns="http://www.w3.org/1998/Math/MathML"> 104 104 </math>104 ( <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 x 68 0x68 </math>0x68) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 104 ≡ 0 ( m o d 8 ) 104\equiv 0\pmod 8 </math>104≡0(mod8) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 × 2 = 0 0\times 2 = 0 </math>0×2=0 | ![]()  | 
'd' <math xmlns="http://www.w3.org/1998/Math/MathML"> → \to </math>→ "岛" | 
<math xmlns="http://www.w3.org/1998/Math/MathML"> 100 100 </math>100 ( <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 x 64 0x64 </math>0x64) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 100 ≡ 4 ( m o d 8 ) 100\equiv 4\pmod 8 </math>100≡4(mod8) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 × 2 = 8 4 \times 2 = 8 </math>4×2=8 | ![]()  | 
一开始 table 字段中是 16 个 null 值 ⬇️ 
| <math xmlns="http://www.w3.org/1998/Math/MathML"> k e y key </math>key | <math xmlns="http://www.w3.org/1998/Math/MathML"> v a l u e value </math>value | hashCode of key | 
hashCode <math xmlns="http://www.w3.org/1998/Math/MathML"> ( m o d 8 ) \pmod 8 </math>(mod8) | 
key 对应的 idx | key 保存到了哪个 idx? | 
|---|---|---|---|---|---|
'g' | 
"高" | 
<math xmlns="http://www.w3.org/1998/Math/MathML"> 103 103 </math>103 | <math xmlns="http://www.w3.org/1998/Math/MathML"> 103 ≡ 7 ( m o d 8 ) 103\equiv 7\pmod 8 </math>103≡7(mod8) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 7 × 2 = 14 7\times 2 = 14 </math>7×2=14 | <math xmlns="http://www.w3.org/1998/Math/MathML"> 14 14 </math>14 | 
'w' | 
"温" | 
<math xmlns="http://www.w3.org/1998/Math/MathML"> 119 119 </math>119 | <math xmlns="http://www.w3.org/1998/Math/MathML"> 119 ≡ 7 ( m o d 8 ) 119\equiv 7\pmod 8 </math>119≡7(mod8) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 7 × 2 = 14 7\times 2 = 14 </math>7×2=14 | <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 0 </math>0 (因为'g' 已经占据了下标为 $14 的位置) | 
'h' | 
"海" | 
<math xmlns="http://www.w3.org/1998/Math/MathML"> 104 104 </math>104 | <math xmlns="http://www.w3.org/1998/Math/MathML"> 104 ≡ 0 ( m o d 8 ) 104\equiv 0\pmod 8 </math>104≡0(mod8) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 × 2 = 0 0\times 2 = 0 </math>0×2=0 | <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 2 </math>2 (因为'w' 已经占据了下标为 $0 的位置) | 
'd' | 
"岛" | 
<math xmlns="http://www.w3.org/1998/Math/MathML"> 100 100 </math>100 | <math xmlns="http://www.w3.org/1998/Math/MathML"> 100 ≡ 4 ( m o d 8 ) 100\equiv 4\pmod 8 </math>100≡4(mod8) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 × 2 = 8 4\times 2 = 8 </math>4×2=8 | <math xmlns="http://www.w3.org/1998/Math/MathML"> 8 8 </math>8 | 
小结
MapN 中有 table 字段,如果我们要在 table 中保存 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 个 key-value pair,那么 table.length 会是 <math xmlns="http://www.w3.org/1998/Math/MathML"> n × 4 n\times 4 </math>n×4。根据 key 的 hashCode,可以计算出 key/value 应该保存在什么位置(如果遇到冲突,则使用 开放寻址法 来找到合适的下标)。



