【问题标题】:hashing in Java -- structure & access timeJava中的散列——结构和访问时间
【发布时间】:2013-08-02 23:12:14
【问题描述】:

我正在寻找对两个不同但相关的论点的验证——那些高于 (A) 和低于 (B) 的第一行 line-comment 在 Q .

(A) HashMap 的结构方式是:

HashMap 是一个普通的表。那就是直接内存访问(DMA)。

HashMap(或一般的散列)背后的整个想法首先 就是把这个恒定时间的内存访问用于

a.) 通过自己的数据内容访问记录(), 不是通过它们在 DMA 中的位置(表索引)

b.) 管理可变数量的记录—— 没有给定大小的记录,并且可能/不会保持不变 在整个使用这种结构的大小。

所以,Java Hash 中的整体结构是:

a table: table // 我正在使用 HashMap

中使用的标识符

此表的每个单元格都是一个

每个bucket都是一个Entry类型的链表—— 即这个链表的每个节点(不是Java/API的链表,而是数据结构)的类型是Entry,它又是一个对。

当一个新的对被添加到哈希中时, 为这个 对计算一个唯一的 hashCode。 这个 hashCodetable 中这个 的索引的关键——它告诉 这个 将在哈希中进入哪个桶。 注意:hashCode 是通过函数 hash() 进行“标准化”的(在 HashMap 中) 以更好地适应 表格 的当前长度。 indexFor() 也在使用中 确定 将进入哪个桶,即表的单元格。

当bucket确定后,被添加到这个bucket中链表的开头--结果,它是这个bucket中的第一个条目,也是第一个条目已经存在的链表现在是 这个新添加的条目指向的“下一个”条目。

//=============================================== ===================

(B) 从我在 HashMap 中看到的情况来看,table 的大小调整 - 哈希仅在基于以下决定的情况下完成 哈希大小和容量,它们是当前的和最大的。 # 整个哈希中的条目。

没有对单个存储桶大小进行重组或调整大小 - 例如“当存储桶中的 max.#entries 超过此类时的 resize()”。

这不太可能,但是有可能大量条目可能会堆积在一个桶中,而其余的哈希几乎是空的。

如果是这种情况,即每个桶的大小没有上限,哈希不是恒定的而是线性访问——理论上是为了一件事。获取哈希中的条目需要 $O(n)$ 时间,其中 $n$ 是条目的总数。但那不应该。

//=============================================== ===================

我认为我没有遗漏上述 (A) 部分中的任何内容。

我不完全确定 (B) 部分。这是一个重要的问题,我正在寻找这个论点的准确性。

我正在寻找对这两个部分的验证。

提前致谢。

//=============================================== ===================

编辑:

最大存储桶大小是固定的,即,无论何时,哈希都会被重组 存储桶中的#entries 达到最大值将解决它——访问时间很简单 在理论上和使用中是恒定的。

这不是一个结构良好但快速的解决方案,并且可以正常访问以进行持续访问。

hashCodes 很可能均匀分布在整个存储桶中,而且不太可能 在达到哈希整体大小的阈值之前,任何一个桶都将达到桶最大值。 这也是当前 HashMap 设置使用的假设。

也基于下面 Peter Lawrey 的讨论。

【问题讨论】:

    标签: java hash time-complexity


    【解决方案1】:

    HashMap 中的冲突仅在拒绝服务攻击等病态情况下才会出现问题。

    在 Java 7 中,您可以更改散列策略,以使外部方无法预测您的散列算法。

    AFAIK,在 Java 8 中,字符串键的 HashMap 将使用树映射而不是链表来解决冲突。这意味着 O(ln N) 最坏情况而不是 O(n) 访问时间。

    【讨论】:

    • 您如何准确定义冲突 - 存储桶大小太大或键的多个值?在这两种情况下,'HashMap' 都不用担心。
    • 我将冲突定义为多个键放置在同一个存储桶中。 HashMap 假设你有一个不错的 hashCode() 算法并且你可以控制你的键值。这通常是正确的,但并非总是如此。
    • 最坏情况下的时间只是拒绝服务攻击的真正问题。即您不控制按键并且有人故意试图减慢您的速度。如果不是这种情况,您可以假设时间接近恒定。
    • 我同意预期的时间复杂度是恒定的或“接近恒定的”。铲斗极度不平衡的可能性非常低。但理论上,访问时间不是恒定的。
    • @Roam 请参阅en.wikipedia.org/wiki/Hash_table - 如果您创建 1000 个对象,其中 hashCode() 每个返回 42,那么您的表中必须有一个包含 1000 个元素的存储桶。桌子的大小无关紧要。从对象的hashCode() 和表大小确定存储桶索引的函数必须如果hashCode() 返回相同的值或者您将不再有哈希映射,则将每个元素放入同一个存储桶中
    【解决方案2】:

    当所有内容都在同一个哈希中时,我希望增加表大小。哈希到桶的映射会随着表的大小而改变。

    你的想法听起来不错。这是完全正确的,基本上当表大小小于预期/每个桶的平均元素数量变得太大时,HashMap 会做什么。 这不是通过查看每个桶并检查其中是否有太多东西来做到这一点,因为它很容易计算。

    根据this在OpenJDK中HashMap.get()的实现是

    public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        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.equals(k)))
                return e.value;
        }
        return null;
    }
    

    这显示了 HashMap 如何很好地找到元素,但它的编写方式非常混乱。经过一些重命名、注释和重写后,它可能大致如下所示:

    public V get(Object key) {
        if (key == null)
            return getForNullKey();
    
        // get key's hash & try to fix the distribution.
        // -> this can modify every 42 that goes in into a 9
        // but can't change it once to a 9 once to 8
        int hash = hash(key.hashCode());
    
        // calculate bucket index, same hash must result in same index as well
        // since table length is fixed at this point.
        int bucketIndex = indexFor(hash, table.length);
        // we have just found the right bucket. O(1) so far.
        // and this is the whole point of hash based lookup:
        // instantly knowing the nearly exact position where to find the element.
    
    
        // next see if key is found in the bucket > get the list in the bucket
        LinkedList<Entry> bucketContentList = table[bucketIndex];
    
        // check each element, in worst case O(n) time if everything is in this bucket.
        for (Entry entry : bucketContentList) {
            if (entry.key.equals(key))
                return entry.value;
        }
        return null;
    }
    

    我们在这里看到的是,bucket 确实取决于从每个 key 对象返回的 .hashCode() 和当前表大小。它通常会改变。但仅在 .hashCode() 不同的情况下。

    如果您有一个包含 2^32 个元素的巨大表格,您可以简单地说 bucketIndex = key.hashCode(),它会尽可能完美。不幸的是,没有足够的内存来执行此操作,因此您必须使用更少的存储桶并将 2^32 哈希映射到几个存储桶中。这就是indexFor 本质上所做的。将大数空间映射成小数空间。

    在(几乎)没有对象具有与任何其他对象相同的.hashCode() 的典型情况下,这完全没问题。但是你不能用 HashMaps 做的一件事是只添加具有完全相同哈希的元素。

    如果每个哈希值都相同,则基于哈希值的查找会产生相同的存储桶,并且您的所有 HashMap 都变成了 LinkedList(或包含存储桶元素的任何数据结构)。现在您遇到了 O(N) 访问时间的最坏情况,因为您必须遍历所有 N 个元素。

    【讨论】:

      猜你喜欢
      • 2017-04-18
      • 2013-02-05
      • 1970-01-01
      • 1970-01-01
      • 2014-09-28
      • 2011-12-31
      • 2015-01-16
      • 1970-01-01
      • 2018-04-27
      相关资源
      最近更新 更多