哈希:它在内部是如何工作的?

Posted

技术标签:

【中文标题】哈希:它在内部是如何工作的?【英文标题】:Hash : How does it work internally? 【发布时间】:2011-05-26 02:32:15 【问题描述】:

这听起来像是一个非常模糊的问题,但事实并非如此。我已经浏览了 wiki 上的 Hash Function 描述,但理解起来并没有多大帮助。

我正在为诸如散列等相当复杂的主题寻找简单的答案。以下是我的问题:

    散列是什么意思?它在内部是如何运作的? 它遵循什么算法? HashMapHashTableHashList 有什么区别? “恒定时间复杂度”是什么意思?为什么哈希的不同实现会提供恒定时间操作? 最后,为什么在大多数面试问题中都会问HashLinkedList,从测试受访者的知识来看有什么具体的逻辑吗?

我知道我的问题列表很大,但如果我能得到这些问题的明确答案,我将不胜感激,因为我真的很想了解这个主题。

【问题讨论】:

在***上尝试Hash table。哈希函数用作该过程的一部分,但不解释哈希表“如何”工作。 在 Java 或我所知道的任何其他语言中都没有 HashList 这样的东西。不要对非代码文本使用代码格式。 【参考方案1】:

    Here 是关于散列的一个很好的解释。例如,您想存储字符串“Rachel”,您对该字符串应用哈希函数以获取内存位置。 myHashFunction(key: "Rachel" value: "Rachel") --> 10。该函数可能会为输入“Rachel”返回 10,因此假设您有一个大小为 100 的数组,您将“Rachel”存储在索引 10 处。如果您想检索该元素,您只需调用 GetmyHashFunction("Rachel"),它将返回 10。请注意,对于此示例,键是“Rachel”,值是“Rachel”,但您可以为该键使用另一个值,例如出生日期或对象。您的哈希函数可能会为两个不同的输入返回相同的内存位置,在这种情况下,如果您正在实现自己的哈希表,您可能会遇到冲突,您可能需要使用链表或其他技术来处理这个问题。

    Here 是一些常用的散列函数。一个好的散列函数满足:每个键都有可能散列到 n 个内存槽中的任何一个,而与任何其他键散列到的位置无关。其中一种方法称为除法。我们通过将 k 的余数除以 n 将密钥 k 映射到 n 个插槽之一。 h(k) = k mod n。例如,如果您的数组大小是n = 100,而您的键是整数k = 15,那么h(k) = 10

    Hashtable 已同步,而 Hashmap 未同步。 Hashmap 允许空值作为键,但 Hashtable 不允许。

    哈希表的目的是在添加和获取元素时具有 O(c) 恒定时间复杂度。在大小为 N 的链表中,如果要获取最后一个元素,则必须遍历所有列表直到得到它,因此复杂度为 O(N)。使用哈希表,如果您想检索一个元素,您只需传递键,哈希函数将返回您所需的元素。如果哈希函数实现得很好,它将在恒定时间内 O(c) 这意味着您不必遍历存储在哈希表中的所有元素。您将“立即”获得元素。

    当然,程序员/开发计算机科学家需要了解数据结构和复杂性 =)

【讨论】:

您提供的两个链接都将我带到我已经访问过的维基页面,并提到我已经浏览过它们,所以你能更新你的前 2 点吗? 感谢您更新答案,现在可以从中得到更多。 没有“O(c) 时间复杂度”之类的东西:你的意思是 O(1)。【参考方案2】:
    散列意味着生成一个(希望是)唯一的数字来代表一个值。 不同类型的值(IntegerString 等)使用不同的算法来计算哈希码。 HashMap 和 HashTable 是 映射;它们是 unqiue 键的集合,每个键都与一个值相关联。 Java 没有 HashList 类。 HashSet 是一组唯一值。 从哈希表中获取项目的时间与表的大小有关。 计算哈希值不一定是关于被哈希值的恒定时间。 例如,计算字符串的哈希值涉及到迭代字符串,并且就字符串的大小而言不是常数时间。 这些都是人们应该知道的事情。

【讨论】:

不,不会。不可能为每个可能的字符串生成唯一的 32 位 数字。这就是存在碰撞的原因。 好的。计算hashcodelong 的算法是什么 @Rachel:哈希不要尝试生成唯一编号。它尝试为输出创建同质分布,以便每个输出值大致具有1/nr_of_possible_hash_values 概率。 通常您可以将数字直接用作哈希码。 long 的范围有限,因此这将是一个完美的哈希函数,具有 1:1 映射且没有冲突。 @Rachel - @ruslik 的意思是:哈希输出一个数字,该数字在特定的数字范围内。例如,该数字可能介于 0 和 2^32-1 之间。当您散列一些数据时,会返回该范围内的一个数字,理想情况下,每个可能的数字都同样可能被返回。您想要哈希表/映射的原因是该表可以被认为是一个数组。当您散列一个值时,您使用该数字作为数组中的索引。您想避免两次使用相同的索引。如果数字均匀分布,则碰撞的可能性较小。【参考方案3】:

    散列将给定的实体(在 Java 术语中 - 一个对象)转换为某个数字(或序列)。散列函数是不可逆的 - 即您无法从散列中获取原始对象。在内部实现(对于java.lang.Object,通过 JVM 获取一些内存地址。

    JVM 地址是不重要的细节。每个类都可以用自己的算法覆盖hashCode() 方法。 Modren Java IDE 允许生成良好的 hashCode 方法。

    Hashtable 和 hashmap 是一回事。它们是键值对,其中键被散列。哈希列表和哈希集不存储值 - 仅存储键。

    恒定时间意味着无论哈希表(或任何其他集合)中有多少条目,通过其键查找给定对象所需的操作数是恒定的。即 - 1,或接近 1

    这是基本的计算机科学资料,相信大家都熟悉。我认为谷歌已经指定哈希表是计算机科学中最重要的数据结构。

【讨论】:

你能举例算法实现从 long 生成哈希码函数吗?另外我在面试中被问到生成哈希码的算法是什么,我不确定当前内部的工作情况,所以想了解它是如何在内部完成的。 您可以查看java.lang.Long 的文档 - 代码只有一行:return (int)(value ^ (value >>> 32));【参考方案4】:

我将尝试对散列及其用途进行简单的解释。

首先,考虑一个简单的列表。此类列表上的每个操作(插入、查找、删除)都将具有 O(n) 复杂度,这意味着您必须解析整个列表(或平均其中一半)才能执行此类操作。

散列是一种非常简单有效的加速方法:考虑我们将整个列表拆分为一组小列表。在这样一个小列表中的项目会有一些共同点,而这点可以从密钥中推断出来。例如,通过有一个名称列表,我们可以使用第一个字母作为选择在哪个小列表中查找的质量。这样,通过按键的第一个字母对数据进行分区,我们获得了一个简单的哈希,它可以将整个列表拆分为大约 30 个较小的列表,因此每个操作将花费 O(n)/30 时间.

但是,我们可以注意到结果并不是那么完美。首先,它们只有 30 个,我们无法更改。其次,某些字母的使用频率高于其他字母,因此YZ 的集合将比A 的集合小得多。为了获得更好的结果,最好找到一种将项目划分为大致相同大小的集合的方法。我们怎么能解决这个问题?这是您使用哈希函数的地方。它是这样一个函数,能够创建任意数量的分区,每个分区中的项目数量大致相同。在我们的名称示例中,我们可以使用类似

int hash(const char* str)
    int rez = 0;
    for (int i = 0; i < strlen(str); i++)
        rez = rez * 37 + str[i];
    return rez % NUMBER_OF_PARTITIONS;
;

这将确保相当均匀的分布和可配置数量的集合(也称为存储桶)。

【讨论】:

【参考方案5】:

我们所说的哈希是什么意思,如何 它在内部工作?

散列是对表示原始字符串的较短固定长度值或键的字符串的转换。它不是索引。哈希的核心是哈希表。它包含项目数组。哈希表包含来自数据项键的索引,并使用该索引将数据放入数组中。

它遵循什么算法?

简单来说,大多数 Hash 算法的工作逻辑是“index = f(key, arrayLength)”

最后,为什么在大多数采访中 问题 Hash 和 LinkedList 是 问一下,有没有具体的逻辑 它来自测试受访者的 知识?

这是关于你在逻辑推理方面的能力。这是每个程序员都知道的最重要的数据结构。

【讨论】:

以上是关于哈希:它在内部是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

java NIO内部如何工作,内部使用线程池吗?

Java 分析器在内部是如何工作的?

sklearn 的 MLP predict_proba 函数在内部是如何工作的?

SqlDataAdapter 如何在内部工作?

SignalR 如何在内部工作?

PHP 的“未设置”构造如何在内部工作?