关于 LinkedList 节点的 HashTable 性能的问题

Question regarding performance of HashTable of LinkedList Nodes

我在 Class 的 init 上实现了一个具有可变大小桶的哈希表,只是一个 linked 列表的数组在运行时大小。

问题在于,对于必须遍历 linked-list 的少量存储桶(深度可以达到大约 5K 个节点),其性能优于具有更多存储桶的哈希表,其三个顺序不同幅度更大。

    int SMALL_BUCKET_SIZE = 10;
    int BIG_BUCKET_SIZE = 10000;

    HashTable<String, Integer> smallHashTable = new HashTable<>(SMALL_BUCKET_SIZE);
    HashTable<String, Integer> bigHashtTable = new HashTable<>(BIG_BUCKET_SIZE);

我希望较大的 HashTable 的搜索时间复杂度为 O(1),其中较小的哈希table 具有较高的冲突率,由于遍历 linked 节点需要更多时间,但是我下面的数字显示较小的 table 优于较宽的 table.

Fetch SmallTable: 0.000007
Fetch BigTable: 0.000018

所以我决定将 HashTable.get 循环一千次以考虑 JIT 和 JVM 优化。现在我开始看到似乎证实了我的预期的数字。

Fetch SmallTable: 0.0000013630
Fetch BigTable: 0.0000002560

我的问题是关于我的逻辑的合理性以及这里的其他活动部分。我已将我的测试与 link 一起粘贴到 HashTable 和底层节点结构的实现中。

从这里的人那里寻找 depth/experience,他们可能能够提供有关影响此因素的变量的交互式反馈,例如密钥长度和哈希冲突率、存储桶密度等

HashTableTest.java

@Test
public void canInitializeHashTableWithBucketsForPerformance() throws InterruptedException {
    double smallTableTime, bigTableTime;
    int SMALL_BUCKET_SIZE = 10;
    int BIG_BUCKET_SIZE = 10000;

    HashTable<String, Integer> smallHashTable = new HashTable<>(SMALL_BUCKET_SIZE);
    HashTable<String, Integer> bigHashtTable = new HashTable<>(BIG_BUCKET_SIZE);
    List<String> strings = generateRandomStringKeys(1000);

    strings.forEach(string -> bigHashtTable.put(string, 10));
    strings.forEach(string -> smallHashTable.put(string, 10));

    Consumer<String> bigHashGet = bigHashtTable::get;
    Consumer<String> smallHashGet = smallHashTable::get;

    String theString = strings.get(strings.size() - 1);

    smallTableTime = getElapsedTimeFactoringOutJavaOptimization(theString, smallHashGet);
    bigTableTime = getElapsedTimeFactoringOutJavaOptimization(theString, bigHashGet);

    System.out.println(String.format("Fetch SmallTable: %.10f", smallTableTime));
    System.out.println(String.format("Fetch BigTable:   %.10f", bigTableTime));

    assertTrue(smallTableTime > bigTableTime);
}

public double getElapsedTimeFactoringOutJavaOptimization(String s, Consumer<String> aMethod) {
    long start = 0, end = 0;

    for (int i = 0; i < 1000; i++) {
        start = System.nanoTime();
        aMethod.accept(s);
        end = System.nanoTime();
    }

    return (end - start) / 1_000_000_000D;
}

public List<String> generateRandomStringKeys(int numOfRandomKeys) {
    List<String> keys = new ArrayList<>();

    for (int i = 0; i < numOfRandomKeys; i++) {
        byte[] array = new byte[10];
        new Random().nextBytes(array);
        keys.add(new String(array, Charset.forName("UTF-8")));
    }

    return keys;
}

可以在这里找到测试 - Github - HashTableTest.java

也可以在这里找到实现 - Github - HashTable.java

这里有很多错误,但有少数包括:

  • 运行 此操作 1000 次并为它们中的每一个取 nanoTime 的差异不会使您的基准有效。说真的,使用 JMH。或者至少 运行 一千万次。
  • 对于不同大小的 table,您的散列 table 实际上并没有任何不同。您使用 table[getHash(key) % RADIX],这基本上意味着 然而 大 table 是,您只使用其中的 10 个桶并假装其余的不存在。
  • System.identityHashCode 不是一个有用的散列函数,尤其是在字符串上,尤其是当您希望真正找到其中存在的元素时……或不存在。
  • 当您使用它时,您并没有将 Node.next 用作一个字段,不妨摆脱它。