C 字符串哈希散列字符串或内存地址吗?
Does C++ string hashing hash the string or the memory address?
我以前从未研究过哈希算法,我很惊讶地,当使用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变量来启动无序地图,请确保其寿命合适。
- C++ 将地址保存为字符串的向量转换为新的向量
- 编译器如何将链表中的地址字符串在一起?
- 为什么cout输出内存地址不是字符串?
- 在字符串函数的指针中返回地址
- 在带有 (void*) 强制转换的字符串中打印字符的内存地址
- 为什么调用 cout.operator<<(const char*) 打印地址而不是字符串? 如何创建一个函数本地静态"HashSet<char>"并初始化它一次?
- 将指针分配给字符串变量中包含的地址
- C 给出了一个字符串列表,如何从类中获取各个变量地址
- 将 char[ ][ ] 的地址复制到 C 字符串
- 如何从地址获取字符串?
- 返回类型 char* 的成员函数返回在 while 循环后包含不同字符串的地址
- 无符号的 int 到 IP 地址字符串,不带 itoa/to_string/boost
- 为什么此代码打印字符串而不是打印地址
- 如何将字符串地址移至C 中的函数
- 为什么'std::string'中第一个元素的地址打印为整个字符串?
- 如何将字符串转换为地址
- 访问字符串元素地址
- C++为什么字符串的地址尽管是十六进制格式,但不能存储在 long int 变量中?
- 将指针的地址转换为字符串,并将字符串地址分配给指针
- 复制函数打印字符串地址,而不是字符串内容