我需要将字符串转换为某种哈希形式。在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;
}
const str = 'revenue'
console.log(str, str.hashCode())
hash << 5 - hash
相当于 hash * 31 + char
,但速度要快得多。这很好,因为它非常快,而且31是一个小质数。双赢! - corsiKa(hash * 31) + char
生成的输出与基于位移的代码 ((hash<<5)-hash)+char
生成的输出完全相同,即使对于非常长的字符串(我已经测试过包含一百万个字符的字符串),因此从准确性上来说它并不是“无法使用的”。无论是数字版本还是位移版本,复杂度都是O(n),因此从复杂度上来说它也不是“无法使用的”。 - TachyonVortexif (this.length == 0) return hash;
是否会对性能产生重大影响。就代码的整洁程度而言,在我看来,它只是噪音。 - user40171这里的许多答案都是来自Java的String.hashCode
哈希函数。它最初来自于Gosling Emacs 1981年的版本,非常薄弱,在现代JavaScript中在性能上毫无意义。实际上,通过使用ES6 Math.imul
,实现可以显著地更快,但没有人注意到。我们可以做得比这好得多,性能也基本相同。
我写了一个——cyrb53,它是一个简单但高质量的53位哈希函数。它非常快速,提供非常好*的哈希分布,并且因为它输出53位,与任何32位哈希相比,具有显着较低的冲突率。此外,您可以忽略SA的CC许可证,因为它在我的GitHub上是公共领域。
const cyrb53 = (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);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};
console.log(`cyrb53('a') -> ${cyrb53('a')}`)
console.log(`cyrb53('b') -> ${cyrb53('b')}`)
console.log(`cyrb53('revenge') -> ${cyrb53('revenge')}`)
console.log(`cyrb53('revenue') -> ${cyrb53('revenue')}`)
console.log(`cyrb53('revenue', 1) -> ${cyrb53('revenue', 1)}`)
console.log(`cyrb53('revenue', 2) -> ${cyrb53('revenue', 2)}`)
console.log(`cyrb53('revenue', 3) -> ${cyrb53('revenue', 3)}`)
*这个算法与著名的MurmurHash/xxHash算法大致相似。它使用乘法和Xorshift的组合来生成哈希值,但不像那么彻底。因此,它的实现要简单得多,但可能无法通过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
或Number
慢得多。
仅为了好玩,这里是 TinySimpleHash,我能想到的最小的散列算法,但它仍然足够好。它是一个32位的散列算法,在只有89个字符的情况下,比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}
编辑
根据我的 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);
}
// testing
console.log(hashCode("hello."));
console.log(hashCode("this is a text."));
console.log(hashCode("Despacito by Luis Fonsi"));
单行箭头函数版本:
hashCode = s => s.split('').reduce((a,b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)
// testing
console.log(hashCode("hello."));
console.log(hashCode("this is a text."));
console.log(hashCode("Despacito by Luis Fonsi"));
String.hashCode()
算法似乎是DJB2的变体。
以下是一些使用大输入字符串的基准测试: http://jsperf.com/32-bit-hash
当哈希短输入字符串时,相对于DJ2B和FNV-1a,murmur的性能会下降: http://jsperf.com/32-bit-hash/3
因此,一般我会推荐murmur3。
JavaScript实现请参见此处:
https://github.com/garycourt/murmurhash-js
如果输入字符串很短且性能比分布质量更重要,则使用DJB2(由esmiralha的答案所建议)。
如果质量和小代码大小比速度更重要,我使用这个FNV-1a实现(基于this code)。
/**
* 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)
}
使用时请谨慎,不要期望过高。
hash("example1") - hash("example2") == 1
,而这个函数则更加不可预测。 - GavinoGrifoniMath.imul
函数实现,FNV1a可以非常快。仅凭这一点就使它排名前列,并且从长远来看比DJB2更好的选择。 - bryc基于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!'))
str += ""
以避免传递非字符串参数时抛出 str.split is not a function
异常。 - BeetleJuicehash |= 0
将其转换为32位整数。而这个实现没有这样做。这是一个错误吗? - Sukima我有点惊讶,没有人谈论过新的SubtleCrypto API。
要从字符串获取哈希值,您可以使用subtle.digest
方法:
function getHash(str, algo = "SHA-256") {
let strBuf = new TextEncoder().encode(str);
return crypto.subtle.digest(algo, strBuf)
.then(hash => {
window.hash = hash;
// here hash is an arrayBuffer,
// so we'll connvert it to its hex version
let result = '';
const view = new DataView(hash);
for (let i = 0; i < hash.byteLength; i += 4) {
result += ('00000000' + view.getUint32(i).toString(16)).slice(-8);
}
return result;
});
}
getHash('hello world')
.then(hash => {
console.log(hash);
});
var promise = crypto.subtle.digest({name: "SHA-256"}, Uint8Array.from(data)); promise.then(function(result){ console.log(Array.prototype.map.call(new Uint8Array(result), x => x.toString(16).padStart(2, '0')).join('')); });
- Denis Giffelercrypto
有些过头了。crypto
并不是特别高效的。 - brycresult += ('00000000' + view.getUint32(i).toString(16)).slice(-8);
感到困惑。为什么要添加 8 个零 00000000
,然后在最后将它们切掉?.slice(-8)
是什么意思?当我删除这些部分时,在我的非常有限的测试中,我得到了完全相同的结果。我错过了什么吗? - I0_olview = new DataView(new Uint32Array([0]).buffer)
。你将得到没有填充的 "0"
和带有 "00000000"
填充的结果。 - Kaiido这是更加精细和高效的变种,并且与Java标准object.hashCode()
在CharSequence
方面有着相同的实现。
String.prototype.hashCode = function() {
var hash = 0, i = 0, len = this.length;
while ( i < len ) {
hash = ((hash << 5) - hash + this.charCodeAt(i++)) << 0;
}
return hash;
};
这里还有一个仅返回正数哈希码的函数:
String.prototype.hashcode = function() {
return this.hashCode()+ 2147483647 + 1;
};
这里有一个与Java匹配的版本,仅返回正数哈希码:
public static long hashcode(Object obj) {
return ((long) obj.hashCode()) + Integer.MAX_VALUE + 1l;
}
没有原型,适用于那些不想将其附加到String
的人:
function hashCode(str) {
var hash = 0, i = 0, len = str.length;
while ( i < len ) {
hash = ((hash << 5) - hash + str.charCodeAt(i++)) << 0;
}
return hash;
}
function hashcode(str) {
hashCode(str) + 2147483647 + 1;
}
尽情享受吧!
如果有人需要的话,我将前两个答案结合起来,形成了一个适用于旧版浏览器的版本。如果reduce
可用,则使用快速版本,并在不可用时退回到esmiralha的解决方案。
/**
* @see https://dev59.com/XWsz5IYBdhLWcg3w2Lpu
* @return {number}
*/
String.prototype.hashCode = function(){
if (Array.prototype.reduce){
return this.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
}
var hash = 0;
if (this.length === 0) return hash;
for (var i = 0; i < this.length; i++) {
var character = this.charCodeAt(i);
hash = ((hash<<5)-hash)+character;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
使用方法如下:
var hash = "some string to be hashed".hashCode();
String.prototype.hashCode = function(){ var hash = 5381; if (this.length === 0) return hash; for (var i = 0; i < this.length; i++) { var character = this.charCodeAt(i); hash = ((hash<<5)+hash)^character; // 转换为32位整数 } return hash; }
- Musakkhir Sayyedreturn a & a
?难道不应该只返回a吗? - James StewartUUID v3 和 UUID v5 实际上是针对给定输入字符串的哈希值。
因此,最明显的选择是选择 UUID v5。
幸运的是,有一个流行的 npm 包,其中包含所有 UUID 算法。
npm install uuid
要生成UUID v5,您需要一个唯一的命名空间。此命名空间类似于种子,并且应该是一个常量,以确保对于给定的输入,输出始终相同。具有讽刺意味的是,您应该生成一个UUID v4作为命名空间。而最简单的方法是使用一些在线工具。
一旦您获得了命名空间,您就可以开始了。
import { v5 as uuidv5 } from 'uuid';
const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';
const hash = uuidv5('input', MY_NAMESPACE);
如果您的输入字符串始终是URL,那么有一些默认的命名空间可以使用。
const hashForURL = uuidv5('https://www.w3.org/', uuidv5.URL);
虽然我来晚了,但你可以使用这个模块:crypto:
const crypto = require('crypto');
const SALT = '$ome$alt';
function generateHash(pass) {
return crypto.createHmac('sha256', SALT)
.update(pass)
.digest('hex');
}
此函数的结果始终是一个长度为64的字符串,类似于这样:"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"