我正在寻找关于两个不同但相关的论点的验证 - 上面(A)以及(B)下面的第一行 - 在Q中的评论。
(A) HashMap 的结构方式是:
HashMap 是一个普通表。那是直接内存访问(DMA)。
首先在 HashMap (或一般哈希)背后的整个想法
是为了使用这种恒定时间内存访问a。)通过他们自己的数据内容(< K,V>)访问记录, 而不是它们在DMA中的位置(表索引)
b。)管理可变数量的记录 - 一些 不具有给定大小的记录,可以/不保持不变 在整个使用这种结构的大小。
因此,Java Hash中的整体结构是:
表格: 表格 //我使用 HashMap
中使用的标识符此表格的每个单元格都是 存储桶 。
每个 存储桶 是条目类型的链接列表 - 即,该链接列表的每个节点(不是Java / API的链接列表,但是数据结构)是条目类型,其又是< K,V>对。
当一对新对进入哈希时, 为此计算唯一的 hashCode K,V>对。 此 hashCode 是此<的索引的关键。 K,V>在 表 - 它告诉我 这个< K,V>将进入哈希。 注意: hashCode 通过函数 hash()进行“规范化”(在 HashMap 中为一个) 更好地适应 表 的当前长度。 indexFor()也在使用中 确定哪个桶,即表格的单元格< K,V>将进去。
当确定桶时,< K,V>被添加到此存储桶中链接列表的开头 - 因此,它是第一个< K,V>此存储桶中的条目以及已存在的链接列表的第一个条目现在是 这个新添加的条目指向的“下一个”条目。
// ============================================= ==================
(B) 从我在 HashMap 中看到的, 表 的大小调整 - 哈希仅在基于 散列大小和容量,即当前和最大值。整个哈希中的#个条目。
没有对单个铲斗尺寸进行重新构造或调整大小 - 例如当存储桶中的最大#个条目超过这样的“大小”时,“resize()”。
这是不可能的,但可能是大量的条目可能在一个桶中堆积,而其余的哈希几乎是空的。
如果是这种情况,即每个桶的大小没有上限,则散列不是常数而是线性访问 - 理论上是一件事。获取哈希条目需要$ O(n)$时间,其中$ n $是条目总数。但那不应该。
// ============================================= ==================
我认为我在上面的(A)部分中没有遗漏任何内容。
我不完全确定(B)部分。这是一个重要问题,我正在寻找这个论点的准确性。
我正在寻找两个部分的验证。
提前致谢。
// ============================================= ==================
编辑:
最大桶大小是固定的,即每当重构时都会重构哈希 存储桶中的#entries达到最大值将解决它 - 访问时间很明显 在理论和使用上都是不变的。
这不是一个结构良好但快速修复,并且为了持续访问而工作得很好。
hashCodes很可能在整个存储桶中均匀分布,并且不太可能 任何桶都会在达到哈希总体大小的阈值之前达到桶最大值。 这是HashMap的当前设置也在使用的假设。
同样基于Peter Lawrey的讨论。
答案 0 :(得分:3)
HashMap中的冲突只是拒绝服务攻击等病态案件中的一个问题。
在Java 7中,您可以更改散列策略,以便外部方无法预测您的散列算法。
AFAIK,在Java 8中,字符串键的HashMap将使用树映射而不是链表来进行冲突。这意味着O(ln N)最坏情况而不是O(n)访问时间。
答案 1 :(得分:1)
我希望在所有内容都在同一个哈希值时增加表大小。当表的大小确实时,hash-to-bucket映射会发生变化。
你的想法听起来不错。并且它完全正确,基本上当表格大小小于期望/每桶的平均元素数量太大时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;
}
我们在这里看到的是,存储桶确实取决于每个密钥对象返回的.hashCode()
和当前表大小。它通常会改变。但仅限于.hashCode()
不同的情况。
如果你有一个包含2 ^ 32个元素的巨大表格,你可以简单地说bucketIndex = key.hashCode()
,它会尽可能完美。遗憾的是没有足够的内存来执行此操作,因此您必须使用更少的存储桶并将2 ^ 32哈希映射到几个存储桶中。这就是indexFor
本质上的作用。将大量空间映射为小空间。
在典型的情况下(几乎)没有任何对象具有相同的.hashCode()
,这是完全正常的。但是你不能用HashMaps做的一件事就是只添加具有完全相同散列的元素。
如果每个散列都相同,那么基于散列的查找会产生相同的存储桶,并且您的所有HashMap都变成了LinkedList(或者任何数据结构都包含存储桶的元素)。现在你有O(N)访问时间的最坏情况,因为你必须迭代所有N个元素。