我需要将字符串转换为某种形式的哈希。这在 JavaScript 中可行吗?
我没有使用服务器端语言,所以我不能那样做。
我需要将字符串转换为某种形式的哈希。这在 JavaScript 中可行吗?
我没有使用服务器端语言,所以我不能那样做。
String.prototype.hashCode = function() {
var hash = 0, i, chr;
if (this.length === 0) return hash;
for (i = 0; i < this.length; i++) {
chr = this.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
};
来源:http : //werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
编辑
根据我的 jsperf 测试,接受的答案实际上更快:http ://jsperf.com/hashcodelordvlad
原来的
如果有人感兴趣,这里有一个改进的(更快)版本,它会在缺少reduce
数组功能的旧浏览器上失败。
hashCode = function(s){
return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
}
单线箭头函数版本:
hashCode = s => s.split('').reduce((a,b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)
这里的许多答案都是String.hashCode
取自 Java的相同哈希函数。它的历史可以追溯到 1981 年的 Gosling Emacs,非常弱,并且在现代 JavaScript 中的性能方面为零意义。事实上,使用 ES6 可以显着加快实现速度Math.imul
,但没有人注意到。我们可以做得比这更好,性能基本相同。
这是我做的一个—— cyrb53,一个简单但高质量的 53 位哈希。它非常快,提供了非常好的*散列分布,并且因为它输出 53 位,与任何32 位散列相比,冲突率显着降低。
const cyrb53 = function(str, seed = 0) {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909);
h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1>>>0);
};
*大致类似于众所周知的MurmurHash/xxHash算法。它使用乘法和Xorshift的组合来生成哈希,但没有那么彻底。因此,它比 JavaScript 中的任何一个都快,并且实现起来要简单得多,但可能无法通过 SMHasher 中的所有测试。这不是加密哈希函数,因此不要出于安全目的使用它。
像任何适当的散列一样,它具有雪崩效应,这基本上意味着输入的微小变化会导致输出的巨大变化,从而使生成的散列看起来更“随机”:
"501c2ba782c97901" = cyrb53("a")
"459eda5bc254d2bf" = cyrb53("b")
"fbce64cc3b748385" = cyrb53("revenge")
"fb1d85148d13f93a" = cyrb53("revenue")
您可以选择为相同输入的备用流提供种子(无符号整数,最大 32 位):
"76fee5e6598ccd5c" = cyrb53("revenue", 1)
"1f672e2831253862" = cyrb53("revenue", 2)
"2b10de31708e6ab7" = cyrb53("revenue", 3)
从技术上讲,它是一个 64 位哈希,即两个不相关的 32 位哈希并行计算,但 JavaScript 仅限于 53 位整数。如果方便,可以通过使用十六进制字符串或数组更改return 语句来使用完整的 64 位输出。
return [h2>>>0, h1>>>0];
// or
return (h2>>>0).toString(16).padStart(8,0)+(h1>>>0).toString(16).padStart(8,0);
// or
return 4294967296n * BigInt(h2) + BigInt(h1);
请注意,构建十六进制字符串会大大减慢批处理速度。数组效率更高,但显然需要两次检查而不是一次检查。我还包括BigInt
,它应该String
比Array
or稍快,但仍然比or慢得多Number
。
只是为了好玩,这里是 TinySimpleHash,我能想出的最小的哈希值仍然不错。它是89 个字符的 32 位散列,具有比 FNV 或 DJB2 更好的质量随机性:
TSH=s=>{for(var i=0,h=9;i<s.length;)h=Math.imul(h^s.charCodeAt(i++),9**9);return h^h>>>9}
注意:即使使用最好的 32 位哈希,冲突迟早会发生。
散列冲突概率可以计算为 ,近似为 (见这里)。这可能比直觉所暗示的要高:
假设一个 32 位哈希和 k=10,000 个项目,发生碰撞的概率为 1.2%。对于 77,163 个样本,概率变为 50%!(计算器)。
我建议底部的解决方法。
在对这个问题的回答中,
哪种散列算法最适合唯一性和速度?, Ian Boyd 发表了一篇很好的深入分析。简而言之(按照我的解释),他得出的结论是 Murmur 最好,其次是 FNV-1a。
esmiralha 提出的 Java 的 String.hashCode() 算法似乎是 DJB2 的变体。
此处使用大输入字符串的一些基准测试:http : //jsperf.com/32-bit-hash
当对短输入字符串进行哈希处理时,相对于 DJ2B 和 FNV-1a,murmur 的性能下降:http ://jsperf.com/32-位哈希/3
所以总的来说,我会推荐 murmur3。
在这里查看 JavaScript 实现:https :
//github.com/garycourt/murmurhash-js
如果输入字符串很短并且性能比分发质量更重要,请使用 DJB2(如 esmiralha 接受的答案所建议的那样)。
如果质量和小代码量比速度更重要,我使用 FNV-1a 的这个实现(基于这个代码)。
/**
* Calculate a 32 bit FNV-1a hash
* Found here: https://gist.github.com/vaiorabbit/5657561
* Ref.: http://isthe.com/chongo/tech/comp/fnv/
*
* @param {string} str the input value
* @param {boolean} [asString=false] set to true to return the hash value as
* 8-digit hex string instead of an integer
* @param {integer} [seed] optionally pass the hash of the previous chunk
* @returns {integer | string}
*/
function hashFnv32a(str, asString, seed) {
/*jshint bitwise:false */
var i, l,
hval = (seed === undefined) ? 0x811c9dc5 : seed;
for (i = 0, l = str.length; i < l; i++) {
hval ^= str.charCodeAt(i);
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
}
if( asString ){
// Convert to 8 digit hex string
return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
}
return hval >>> 0;
}
提高碰撞概率
如此处所述,我们可以使用以下技巧扩展哈希位大小:
function hash64(str) {
var h1 = hash32(str); // returns 32 bit (as 8 byte hex string)
return h1 + hash32(h1 + str); // 64 bit (as 16 byte hex string)
}
小心使用它,但不要期望太多。
基于ES6 中接受的答案。更小、可维护且适用于现代浏览器。
function hashCode(str) {
return str.split('').reduce((prevHash, currVal) =>
(((prevHash << 5) - prevHash) + currVal.charCodeAt(0))|0, 0);
}
// Test
console.log("hashCode(\"Hello!\"): ", hashCode('Hello!'));
编辑(2019-11-04):
单线箭头函数版本:
const hashCode = s => s.split('').reduce((a,b) => (((a << 5) - a) + b.charCodeAt(0))|0, 0)
// test
console.log(hashCode('Hello!'))