[Java] 浅析 Map.of(...) 方法和 Map.ofEntries(...) 方法

362 阅读12分钟

浅析 java 中的 Map.of(...) 方法和 Map.ofEntries(...) 方法

JDK 9java.util.Map 接口中提供了一组 of(...) 静态方法(我用 ... 来表示参数类型/个数在这里不重要)以及一个 ofEntries(...) 方法。通过调用这些静态方法,我们可以构造不可变的(immutable) map

这组 Map.of(...) 方法的参数中,key-value pair 的数量 nn 可以是 {0,1,2,3,4,5,6,7,8,9,10}\{0,1,2,3,4,5,6,7,8,9,10\} 中的任意值。当我们需要提供超过 1010key-value pair 时,可以调用 Map.ofEntries(...) 方法。在 这个方法javadoc 里可以找到如下的例子 ⬇️

image.png

我把代码复制到下方了 ⬇️

import static java.util.Map.entry;

     Map<Integer,String> map = Map.ofEntries(
         entry(1, "a"),
         entry(2, "b"),
         entry(3, "c"),
         ...
         entry(26, "z"));

当您调用这些方法时,是否想过以下问题? ⬇️

  1. Map.of(...)/Map.ofEntries(...) 返回的 Map 的精确类型是什么?是 HashMap 吗,还是其他类型的 Map
  2. 为什么 Map.of(...)/Map.ofEntries(...) 返回的 Map 具有不可变(immutable)的特点,它是如何实现的?
  3. Map.of(...)/Map.ofEntries(...) 所返回的 Map,是如何存储元素的?

本文会从以上 33 个问题入手,对 Map.of(...)/Map.ofEntries(...) 方法进行探索。

要点

  • Map.of(...) 所返回的 Map 的精确类型与参数中 key-value pair个数 nn 有关

    • 如果 n=1n=1 (即 key-value pair 的数量为 11),Map 的精确类型会是 java.util.ImmutableCollections$Map1
    • 如果 n10n \le 10n1n\ne 1 (即 key-value pair 的数量不超过 1010 且不是 11) ,Map 的精确类型会是 java.util.ImmutableCollections$MapN
  • Map.ofEntries(...) 所返回的 Map 的精确类型与参数中 Entry数量 nn 有关

    • 如果 n=1n=1 (即 Entry 的数量为 11),Map 的精确类型会是 java.util.ImmutableCollections$Map1
    • 如果 n1n \ne 1 (即 Entry 的数量不是 11),Map 的精确类型会是 java.util.ImmutableCollections$MapN
  • AbstractImmutableMap 中的 put(...)/remove(...) 等方法会抛出 UnsupportedOperationException,而 Map1MapN 都继承了 AbstractImmutableMap,所以 Map1/MapN 的实例就是不可变(immutable)的了

  • MapN 中有 table 字段,它的 length 会是 key-value pair 的数量的 44 倍。在保存 key-value pair 和查询 key 时,会使用 开放寻址法(open addressing)

我画了简单的思维导图 ⬇️

mindmap
      root("Map.of(...)/Map.ofEntries(...)")
          n1("只有 1 个 key-value pair 时")
            n11("返回的是 Map1 的实例")
            Map1 继承了 AbstractImmutableMap 所以 Map1 的实例是 immutable 的
            Map1 中的 k0 和 v0 字段分别保存了 key 和 value 的值
          n2("key-value pair 的数量不等于 1 时")
            n21("返回的是 MapN 的实例")
            MapN 继承了 AbstractImmutableMap 所以 MapN 的实例是 immutable 的
            n23("MapN 中的 table 字段,使用开放寻址法来保存 key-value pair 和查询 key")
    

正文

问题一:Map.of(...)/Map.ofEntries(...) 返回的 Map 的精确类型是什么?是 HashMap 吗,还是其他类型的 Map

我写了如下的代码来进行探索 ⬇️

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(...) 方法。

javac Question1.java
java Question1

运行结果如下

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$Map1
  • java.util.ImmutableCollections$MapN

但仅凭以上几行代码所列举的情况,并不能说明 Map.of(...)/Map.ofEntries(...) 的返回值是否有可能是其他类的实例,我们去看看源码。

Map.of(...) 方法

Map.java 中可以找到各个 Map.of(...) 方法的代码。

key-value pair 的个数 n=0,1,2,3,4,5,6,7,8,9,10n=0,1,2,3,4,5,6,7,8,9,10 共有 1111 种情况。其中 n=2,3,4,5,6,7,8,9,10n=2,3,4,5,6,7,8,9,10 的情况代码高度雷同,以下只展示 n=0,1,2,3n=0,1,2,3 的情况 ⬇️ (为了节约空间,对应的 javadoc 都略去了)

    
    @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 的个数 nnMap.of(...) 返回什么说明
n=0n=0ImmutableCollections.EMPTY_MAP⬅️ 它是 ImmutableCollections.MapN 类型的
n=1n=1ImmutableCollections.Map1 的实例
2n102\le n \le 10ImmutableCollections.MapN 的实例

n=0n=0 时,Map.of() 返回的也是 java.util.ImmutableCollections$MapN 的实例。所以可以把上方的表格再简化一下,变成下面这样 ⬇️

key-value pair 的个数 nnMap.of(...) 返回什么
n=1n=1java.util.ImmutableCollections$Map1 的实例
n10n \le 10n1n\ne 1java.util.ImmutableCollections$MapN 的实例
Map.ofEntries(...) 方法

我们直接看 Map.ofEntries(...) 的源码 ⬇️

    @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(...) 返回什么说明
00ImmutableCollections.EMPTY_MAP⬅️ 它是 ImmutableCollections.MapN 类型的
11ImmutableCollections.Map1 的实例
2\ge 2ImmutableCollections.MapN 的实例

entries.length=0entries.length=0 时,Map.ofEntries(...) 返回的也是 java.util.ImmutableCollections$MapN 的实例。所以可以把上方的表格再简化一下,变成下面这样 ⬇️

entries.length 的值Map.ofEntries(...) 返回什么
11ImmutableCollections.Map1 的实例
1\ne 1ImmutableCollections.MapN 的实例

Map1/MapN 的类图如下(图中把所有的泛型信息都省略了) ⬇️

classDiagram

Map <|.. AbstractMap
AbstractMap <|-- AbstractImmutableMap
Serializable <|.. AbstractImmutableMap
AbstractImmutableMap <|-- Map1
AbstractImmutableMap <|-- MapN

<<Abstract>> AbstractMap
<<Abstract>> AbstractImmutableMap
<<interface>> Map
<<interface>> Serializable
在上图中的类名/接口名Fully Qualified Name
AbstractImmutableMapjava.util.ImmutableCollections$AbstractImmutableMap
AbstractMapjava.util.AbstractMap
Mapjava.util.Map
Map1java.util.ImmutableCollections$Map1
MapNjava.util.ImmutableCollections$MapN
Serializablejava.io.Serializable
小结

Map.of(...) 所返回的 Map 的精确类型与参数中 key-value pair个数 nn 有关

  • 如果 n=1n=1Map 的精确类型会是 java.util.ImmutableCollections$Map1
  • 如果 n10n \le 10n1n\ne 1Map 的精确类型会是 java.util.ImmutableCollections$MapN

Map.ofEntries(...) 所返回的 Map 的精确类型与参数中 Entry数量 nn 有关

  • 如果 n=1n=1Map 的精确类型会是 java.util.ImmutableCollections$Map1
  • 如果 n1n \ne 1Map 的精确类型会是 java.util.ImmutableCollections$MapN

问题二:为什么 Map.of(...)/Map.ofEntries(...) 返回的 Map 具有不可变(immutable)的特点,它是如何实现的?

我们写些代码来进行分析。 请将以下代码保存为 Question2.java

import java.util.Map;

public class Question2 {
    public static void main(String[] args) {
        Map.of().put('a', 97);
    }
}

如下的命令可以编译 Question2.java 以及运行 Question2 类中的 main(...) 方法。

javac Question2.java
java Question2

运行上述命令后,会看到以下的报错

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)

根据上述报错信息,可以找到抛出异常的位置如下 ⬇️

image.png

image.png

从图中的代码可以看出 clear()/putAll(...)/remove(...) 等方法都会抛出 UnsupportedOperationException

我画了类图来辅助理解 ⬇️ (图中将所有泛型信息都省略了,和 问题二 无关的接口/字段/方法也略去了)

classDiagram
class `java.util.Map`
class `java.util.AbstractMap`
class `java.util.ImmutableCollections$AbstractImmutableMap` 
class `java.util.ImmutableCollections$Map1`
class `java.util.ImmutableCollections$MapN`


`java.util.Map` <|.. `java.util.AbstractMap`
`java.util.AbstractMap` <|-- `java.util.ImmutableCollections$AbstractImmutableMap` 

`java.util.ImmutableCollections$AbstractImmutableMap`  <|-- `java.util.ImmutableCollections$Map1`
`java.util.ImmutableCollections$AbstractImmutableMap`  <|-- `java.util.ImmutableCollections$MapN`

class `java.util.ImmutableCollections$AbstractImmutableMap`  {
  clear() 
  compute(K, java.util.function.BiFunction)
  computeIfAbsent(K, java.util.function.Function);
  computeIfPresent(K, java.util.function.BiFunction)
  merge(K, V, java.util.function.BiFunction)
  put(K, V)
  putAll(java.util.Map)
  putIfAbsent(K, V)
  remove(java.lang.Object)
  remove(java.lang.Object, java.lang.Object)
  replace(K, V)
  replace(K, V, V)
  replaceAll(java.util.function.BiFunction)
}


<<Abstract>> `java.util.AbstractMap`
<<Abstract>> `java.util.ImmutableCollections$AbstractImmutableMap` 
<<interface>> `java.util.Map`

note for `java.util.ImmutableCollections$AbstractImmutableMap` "注意: 在 AbstractImmutableCollection 中展示的 13 个方法都会抛 UnsupportedOperationException"
小结

AbstractImmutableMap 中的 clear()/putAll(...)/remove(...) 等方法会抛出 UnsupportedOperationException,而 Map1MapN 都继承了 AbstractImmutableMap,所以 Map1/MapN 的实例是不可变的(immutable)。

问题三:Map.of(...)/Map.ofEntries(...) 所返回的 Map,是如何存储元素的?

这里需要区分 Map1/MapN 两种情况

Map1

ImmutableCollections.java 中,可以找到 Map1 的代码,其中部分代码如下 ⬇️

    // 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 k0
  • private final V v0
Map1 的构造函数

在调用 Map1(K k0, V v0) 这个构造函数时,

  • k0 参数会被赋给 k0 字段
  • v0 参数会被赋给 v0 字段

Map1 中的 get(Object)/containsKey(Object)/size()/isEmpty() 等方法的逻辑比较直观,这里就不赘述了。

小结

Map1 可以处理 11key-value pair 的情况。

MapN

ImmutableCollections.java 中,可以找到 MapN 的代码,其中部分代码如下 ⬇️

    /**
     * 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[] table
  • final int size

而这两个字段的作用是 ⬇️

  • MapN 的构造函数中,会向 table 字段填充元素(具体是如何填充的,我们等一下再说),而 table 字段是保存各个 key-value pair 的地方。
  • size 字段用于保存 key-value pair 的数量。
MapN 的构造函数

我在构造函数的代码中加了点注释 ⬇️ (代码里原有的注释也没删除)

    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 应该去的位置,v 会保存在 k 之后的那个位置
                int dest = -(idx + 1);
                table[dest] = k;
                table[dest+1] = v;
            }
        }
    }

这里大部分逻辑都很直观,只有 int idx = probe(k); 这一行看起来不太好懂。 这里涉及 开放寻址法(open addressing)。我在 [Java] 浅析 Set.of(...) 方法 一文中对 开放寻址法 进行了介绍,有兴趣的读者可以看一看,本文就不重复介绍它了。我在 probe(Object) 方法里也加了些注释 ⬇️ (代码里原有的注释也没删除)

// 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) 对应的下标总是偶数,而 value 会保存在 key 之后的那个位置
    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)) { // 说明 ek 已经在 idx 这个位置了
            return idx;
        } else if ((idx += 2) == table.length) { // idx 这个下标被其他 key 占据,按照开放寻址法的思路,应该继续检查 (id + 2) 的位置(注意 table 被视为循环数组)
            idx = 0;
        }
    }
}

下面举个例子来说明 MapN 里是如何使用 开放寻址法 来保存 key-value pair 的。

import java.lang.reflect.Field;
import java.util.Map;

public class Question3 {
    public static void main(String[] args) throws Exception {
        // 随便找些对应的汉字(y->ye 夜, w->wu 无, m->mian 眠, c->cang 仓, k->ku 库)
        Map<Character, String> map = Map.of(
                'y', "夜",
                'w', "无",
                'm', "眠",
                'c', "仓",
                'k', "库"
        );
        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;
                if ((i % 2) == 0) {
                    message = String.format("key [%s](hashCode=%s) is at index [%s]", table[i], table[i].hashCode(), i);
                } else {
                    message = String.format("value [%s] is at index [%s]", table[i], i);
                }
                System.out.println(message);
            } else {
                System.out.println("// null is at index [" + i + "]");
            }
        }
    }
}

请将上方的代码保存为 Question3.java。用下方的命令可以编译 Question3.java 并运行 Question3 里的 main(...) 方法。

javac Question3.java
java --add-opens=java.base/java.util=ALL-UNNAMED Question3

运行结果如下

key [m](hashCode=109) is at index [0]
value [眠] is at index [1]
key [y](hashCode=121) is at index [2]
value [夜] is at index [3]
key [c](hashCode=99) is at index [4]
value [仓] is at index [5]
// null is at index [6]
// null is at index [7]
// null is at index [8]
// null is at index [9]
// null is at index [10]
// null is at index [11]
// null is at index [12]
// null is at index [13]
key [k](hashCode=107) is at index [14]
value [库] is at index [15]
// null is at index [16]
// null is at index [17]
key [w](hashCode=119) is at index [18]
value [无] is at index [19]

一开始 table 字段中是 20 个 null 值(因为总共有 n=5n=5key-value pairtable 的长度会是 nn44 倍) ⬇️

mermaid-diagram-2025-11-05-105428.png

下表展示了下面的 55key-value pair 保存到 table 字段的过程 ⬇️

  • 'y' \to "夜"
  • 'w' \to "无"
  • 'm' \to "眠"
  • 'c' \to "仓"
  • 'k' \to "库"
keyvaluekey \to value保存这个 key-value pair 之后,table 的内容
'y' \to "夜"mermaid-diagram-2025-11-05-110434.png
'w' \to "无"mermaid-diagram-2025-11-05-110616.png
'm' \to "眠"mermaid-diagram-2025-11-05-110813.png
'c' \to "仓"mermaid-diagram-2025-11-05-111048.png
'k' \to "库"mermaid-diagram-2025-11-05-111220.png

保存这 55key-value pair 时,会用到每个 keyhashCode,重要的信息梳理如下 ⬇️

keykeyvaluevaluehashCode of keyhashCode (mod10)\pmod {10}key 对应的初始 idxkey 实际保存到了哪个 idx?
'y'"夜"1211211211(mod10)121\equiv 1\pmod {10}1×2=21\times 2 = 222
'w'"无"1191191199(mod10)119\equiv 9\pmod {10}9×2=189\times 2 = 181818
'm'"眠"1091091099(mod10)109\equiv 9\pmod {10}9×2=189\times 2 = 1800 (因为'w' 已经占据了下标为 1818 的位置)
'c'"仓"9999999(mod10)99\equiv 9\pmod {10}9×2=189\times 2 = 1844 (因为下标为 18,0,218,0,2 的位置分别被 'w'/'m'/'y' 占据了
'k'"库"1071071077(mod10)107\equiv 7\pmod {10}7×2=147\times 2 = 141414

在查询一个 key 是否存在时,也会用到 probe(Object) 方法。以 containsKey('c') 为例,

  1. 'c'hashCode9999,而 999(mod10)99\equiv 9\pmod {10},所以需要查看下标为 9×2=189\times 2 = 18 的位置,那里是 'w',没有命中(在下图中用 attempt 1 表示)。
  2. 继续查看下标为 00 的位置,那里是 'm',没有命中(在下图中用 attempt 2 表示)。
  3. 继续查看下标为 22 的位置,那里是 'y',没有命中(在下图中用 attempt 3 表示)。
  4. 继续查看下标为 44 的位置,那里就是 'c',命中(在下图中用 attempt 4 表示)。

image.png

小结

MapN 中有 table 字段,如果我们要在 table 中保存 nnkey-value pair,那么 table.length 会是 n×4n\times 4。根据 keyhashCode,可以计算出 key/value 应该保存在什么位置(如果遇到冲突,则使用 开放寻址法 来找到合适的下标)。

其他

展示 table 字段中 key/value 分布的那几张图是怎么画出来的?

mermaid.live 页面可以绘制 block 图。具体的语法可以参考 Block Diagrams Documentation 一文。用以下代码可以画出其中的一张图(其他图的代码也是类似的,就不赘述了)⬇️

block
    block
        columns 10
            s0["'m'"] s2["'y'"] s4["'c'"] s6["null"] s8["null"] 
            s10["null"] s12["null"] s14["'k'"] s16["null"] s18["'w'"]

            i0["0"] i2["2"] i4["4"] i6["6"] i8["8"]
            i10["10"] i12["12"] i14["14"] i16["16"] i18["18"]

            s1["&quot;眠&quot;"] s3["&quot;夜&quot;"] s5["&quot;仓&quot;"] s7["null"] s9["null"] 
            s11["null"] s13["null"] s15["&quot;库&quot;"] s17["null"] s19["&quot;无&quot;"]

            i1["1"] i3["3"] i5["5"] i7["7"] i9["9"]
            i11["11"] i13["13"] i15["15"] i17["17"] i19["19"]
    end

style s0 fill:#00ff00;
style s1 fill:#00ff00;
style s2 fill:#00ff00;
style s3 fill:#00ff00;
style s4 fill:#00ff00;
style s5 fill:#00ff00;
style s6 fill:#808080;
style s7 fill:#808080;
style s8 fill:#808080;
style s9 fill:#808080;
style s10 fill:#808080;
style s11 fill:#808080;
style s12 fill:#808080;
style s13 fill:#808080;
style s14 fill:#00ff00;
style s15 fill:#00ff00;
style s16 fill:#808080;
style s17 fill:#808080;
style s18 fill:#00ff00;
style s19 fill:#00ff00;

style i0 fill:#0f0,stroke:#fff;
style i1 fill:#0f0,stroke:#fff;
style i2 fill:#0f0,stroke:#fff;
style i3 fill:#0f0,stroke:#fff;
style i4 fill:#0f0,stroke:#fff;
style i5 fill:#0f0,stroke:#fff;
style i6 fill:#fff,stroke:#fff;
style i7 fill:#fff,stroke:#fff;
style i8 fill:#fff,stroke:#fff;
style i9 fill:#fff,stroke:#fff;
style i10 fill:#fff,stroke:#fff;
style i11 fill:#fff,stroke:#fff;
style i12 fill:#fff,stroke:#fff;
style i13 fill:#fff,stroke:#fff;
style i14 fill:#0f0,stroke:#fff;
style i15 fill:#0f0,stroke:#fff;
style i16 fill:#fff,stroke:#fff;
style i17 fill:#fff,stroke:#fff;
style i18 fill:#0f0,stroke:#fff;
style i19 fill:#0f0,stroke:#fff;

image.png

参考资料