C 字符串哈希散列字符串或内存地址吗?

Does C++ string hashing hash the string or the memory address?

本文关键字:字符串 地址 内存 哈希散      更新时间:2023-10-16

我以前从未研究过哈希算法,我很惊讶地,当使用std :: unordered_map时,我发现散布的函数(我认为)实际上哈希内存地址,而不是字符串。如果我错了,请纠正我,但是我只需更改一个原始字符串并将其添加到我的Unordered_map,而当内存地址(指针)与之相同时,它从未添加任何内容。

在下种情况下,是否添加新密钥取决于std :: string是否将重新集中到内存的另一个区域:

std::unordered_map<const char*, char*> myMap;
std::string myString = "Key1";
myMap[myString.c_str()] = "someVal";    // <--- Adds a new key, size is now 1
myString = "Key2";
myMap[myString.c_str()] = "someVal";    // <--- Doesn't add a new key "Key2" didn't need to be reallocated

但是,当我更改字符串时,当我直接在模板中使用std ::字符串时,它确实在我的地图中添加了另一个键,因此这表明unordered_map模板适用于std :: string并实际上hansing字符串本身?如果必须放置字符串本身?

是否慢了

我提出这个问题的原因是,我所看到的教程似乎传达了被哈希的实际字符串本身的含义。即使在堆栈溢出上,我也看到人们说(释义)"不需要检查整个字符串,只有尽可能多的字符"出于绩效原因。

好吧,我得到的印象对于字符串文字和弦的指针显然是错误的,但对于std :: string class?

误以为const char*是字符串。它实际上是一个指针。因此,std::unordered_map<const char*, anything>使用指示器(类型const char*)作为键,而std::hash的专业化对于指示器(哈希地址)作为哈希键。

如果要使用字符串作为键,则应使用std::string,例如std::unordered_map<std::string, anything>


编辑我还应该说,使用指针而不是字符串至少是危险的,但通常是不可能的。它不会做您的想法。问题在于,字符串(字符序列)及其地址(指针)不一定在程序的使用寿命中配对(尽管对于某些const char*对象可能是正确的)。想想以下

std::unordered_map<const char*,int> map;
char str[11] = "bad";
map[str] = 2;           // hashes str = char*
auto x = map["bad"];    // hashes address of "bad"; x!=2

这表明使用地址作为密钥无法正常工作:您无法从字符序列("bad"

获得元素查看std::hash的标准基本专业。const char *没有专业化,因为这只是指向字符数组的指针。但是,对于任何指针类型都有专业化:

template< class T > struct hash<T*>;

这是std::unordered_map使用的内容。它只是哈希地址。


简单地将const char*用作std::unordered_map的键,默认hash 和 equality 是凌乱的,因为默认哈希函数哈哈斯地址,默认的等效函数将比较地址。您应该更喜欢 std::string对于您的密钥,否则您需要执行以下操作:

std::unordered_map<const char*, char*, MyCustomHash, MyCustomEquality> myMap;

c 字符串哈希哈希散布字符串或内存地址?

这个问题实际上是关于 equality vors 身份,取决于您说"字符串"。

  • 等价。如果您是指std::string类,则哈希与内存地址无关。字符串的实际内容是哈希。两个 std::string实例相等,如果内容彼此相等,则产生相同的哈希。

  • 身份。如果您是指在内存中的某些字符的指针,则在内存地址,无论在那里保存了哪些数据。两个"字符串"是相同的,如果它们指向相同的内存位置,则产生相同的哈希。


当您处理字符串时,您几乎总是需要等于比较,并鼓励使用std::string,因为即使数据在不同的内存中,代表相同数据的两个不同的字符串实例也应被视为平等地址,std::string总是为您提供这些语义,无论是哈希还是与myStr1 == myStr2

哈希char const*char*非常危险,因为您遇到了许多实现的行为。字符串文字是此的主要示例。例如,考虑以下程序:

#include <iostream>
int main()
{
    char const *a = "foo";
    char const *b = "foo";
    std::cout << reinterpret_cast<void const*>(a) << "n";
    std::cout << reinterpret_cast<void const*>(b) << "n";
}

C 标准不会告诉您地址是否相同。编译器通常允许您控制此行为。例如,Visual C 具有/GF标志。如果您打开它,地址将相同;否则,他们不会。

这对哈希有很大的后果。在以下程序中,是否将打印1或2的实现定义:

#include <iostream>
#include <unordered_map>
int main()
{
    char const *a = "foo";
    char const *b = "foo";
    std::unordered_map<char const*, char*> myMap;
    myMap[a] = "1";
    myMap[b] = "2";
    std::cout << myMap.size() << "n"; // prints 1 or 2
}

您的代码还实施了定义的行为;不是因为文字,而是以不同的方式:

以及在下面的情况下是否添加新密钥取决于 std::string是否重新定位到内存的另一个区域:

是。您绝对不应从两个不同的std::string实例中获得c_str()指示器,并且假设指示器仅仅是因为std::string实例相同。

如果必须放置字符串本身?

no。我挑战您提出一个现实的用例,您可以实际测量差异。只有这样,它就慢了。否则,这是普通的旧过早优化。

但是还有更多。从技术上讲,与使用整个字符串内容(或大部分部分)计算哈希值相比,哈希单一地址应该快,因为涉及更多数据。这很明显。但是我不确定您会看到执行"昂贵"计算的必要性。这没有魔术。如果您的程序逻辑关心字符串的内容,则必须考虑各个字符。即使从理论上讲,您也应该能够使用不阅读的数据?

或更一般而言,如何放置您没有的东西?


[*] 偶然地,未能考虑这种区别是Java中非常常见的错误的来源,即str1 == str2具有不同语义的语义与str1.equals(str2)

代码的行为正确,因为密钥是const char*。尝试使用std::string作为获取您要寻找的行为的钥匙。

so: std::unordered_map<std::string, char*> myMap;

使用指针作为键可以是解决方案,但仅适用于恒定字符串 - 指针是最简单,最快的哈希。您可以使用不同的const变量来启动无序地图,请确保其寿命合适。