我需要将字符串转换为某种形式的哈希。这在JavaScript中可行吗?
我没有使用服务器端语言,所以我不能那样做。
我需要将字符串转换为某种形式的哈希。这在JavaScript中可行吗?
我没有使用服务器端语言,所以我不能那样做。
Answers:
Object.defineProperty(String.prototype, 'hashCode', {
value: function() {
var hash = 0, i, chr;
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/
hash << 5 - hash
是一样的hash * 31 + char
,但速度快了很多。很好,因为它是如此之快,而且31是一个很小的素数。在那双赢。
(hash * 31) + char
产生的输出与基于移位的代码产生的输出相同((hash<<5)-hash)+char
,即使对于非常长的字符串(我已经用包含超过一百万个字符的字符串对其进行了测试),因此在术语上也不是“不可用的”准确性。对于基于数字的版本和基于移位的版本,复杂度均为O(n),因此就复杂度而言,它并非“无法使用”。
n
最大n
可能不会发生冲突的最大字符串是什么?
var hashCode = function hashCode (str) {etc...}
?然后用作hashCode("mystring")
?
编辑
根据我的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)
注意:即使使用最佳的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: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)
}
请谨慎使用,但不要期望过高。
("0000000" + (hval >>> 0).toString(16)).substr(-8);
呢?那不一样(hval >>> 0).toString(16)
吗?
hval
,(hval >>> 0).toString(16)
可能少于8个字符,因此请使用零填充。我很困惑,因为(hval >>> 0).toString(16)
总是给我一个正好8个字符的字符串。
Math.imul
功能实现FNV1a可以非常快。从长远来看,仅此一项就使其成为顶级基准,并且最终比DJB2更好。
基于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
在将非字符串作为参数传递时引发异常
hash |= 0
转换为32位int。此实现没有。这是错误吗?
几乎一半的答案是Java的实现,
String.hashCode
既不是高质量的也不是超快的。没什么特别的,每个字符仅乘以31。它可以在一行中简单有效地实现,并且使用以下命令可以更快Math.imul
:
hashCode=s=>{for(var i=0,h;i<s.length;i++)h=Math.imul(31,h)+s.charCodeAt(i)|0;return h}
有了这些,就更好了— cyrb53,一个简单但高质量的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中的任何一种都要快,并且实现起来也很简单。
它实现了雪崩(非严格),这基本上意味着输入的细微变化在输出中的大变化,从而使得结果散列看起来是随机的:
0xc2ba782c97901 = cyrb53("a")
0xeda5bc254d2bf = cyrb53("b")
0xe64cc3b748385 = cyrb53("revenge")
0xd85148d13f93a = cyrb53("revenue")
您还可以为相同输入的备用流提供种子:
0xee5e6598ccd5c = cyrb53("revenue", 1)
0x72e2831253862 = cyrb53("revenue", 2)
0x0de31708e6ab7 = cyrb53("revenue", 3)
从技术上讲,它是64位哈希(并行的是两个不相关的32位哈希),但是JavaScript限于53位整数。如果需要,可以通过更改十六进制字符串或数组的返回行来使用完整的64位输出。
请注意,在性能至关重要的情况下,构造十六进制字符串会大大减慢批处理的速度。
return (h2>>>0).toString(16).padStart(8,0)+(h1>>>0).toString(16).padStart(8,0);
// or
return [h2>>>0, h1>>>0];
只是为了好玩,这是一个最小的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}
ch
初始化?
'imul'
。
如果对任何人reduce
都有用,我将前两个答案组合成一个允许浏览器使用的较旧版本,如果可用,它将使用快速版本,如果没有,将使用esmiralha的解决方案。
/**
* @see http://stackoverflow.com/q/7616461/940217
* @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; // Convert to 32bit integer } return hash; }
这是一种精致且性能更好的变体:
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;
};
这符合Java对标准的实现 object.hashCode()
这也是只返回正哈希码的代码:
String.prototype.hashcode = function() {
return (this.hashCode() + 2147483647) + 1;
};
这是Java的匹配项,它仅返回正的哈希码:
public static long hashcode(Object obj) {
return ((long) obj.hashCode()) + Integer.MAX_VALUE + 1l;
}
请享用!
令我惊讶的是,还没有人谈论新的SubtleCrypto API。
要从字符串获取哈希,可以使用subtle.digest
方法:
function getHash(str, algo = "SHA-256") {
let strBuf = new TextEncoder('utf-8').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('')); });
crypto
并不是完全有效。
感谢mar10的示例,我找到了一种在FNV-1a的C#和Javascript中获得相同结果的方法。如果存在unicode字符,则出于性能原因将其上部丢弃。不知道为什么在散列时维护它们会有所帮助,因为目前仅散列url路径。
C#版本
private static readonly UInt32 FNV_OFFSET_32 = 0x811c9dc5; // 2166136261
private static readonly UInt32 FNV_PRIME_32 = 0x1000193; // 16777619
// Unsigned 32bit integer FNV-1a
public static UInt32 HashFnv32u(this string s)
{
// byte[] arr = Encoding.UTF8.GetBytes(s); // 8 bit expanded unicode array
char[] arr = s.ToCharArray(); // 16 bit unicode is native .net
UInt32 hash = FNV_OFFSET_32;
for (var i = 0; i < s.Length; i++)
{
// Strips unicode bits, only the lower 8 bits of the values are used
hash = hash ^ unchecked((byte)(arr[i] & 0xFF));
hash = hash * FNV_PRIME_32;
}
return hash;
}
// Signed hash for storing in SQL Server
public static Int32 HashFnv32s(this string s)
{
return unchecked((int)s.HashFnv32u());
}
JavaScript版本
var utils = utils || {};
utils.FNV_OFFSET_32 = 0x811c9dc5;
utils.hashFnv32a = function (input) {
var hval = utils.FNV_OFFSET_32;
// Strips unicode bits, only the lower 8 bits of the values are used
for (var i = 0; i < input.length; i++) {
hval = hval ^ (input.charCodeAt(i) & 0xFF);
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
}
return hval >>> 0;
}
utils.toHex = function (val) {
return ("0000000" + (val >>> 0).toString(16)).substr(-8);
}
我需要一个类似的函数(但有所不同)来根据用户名和当前时间生成一个唯一的ID。所以:
window.newId = ->
# create a number based on the username
unless window.userNumber?
window.userNumber = 0
for c,i in window.MyNamespace.userName
char = window.MyNamespace.userName.charCodeAt(i)
window.MyNamespace.userNumber+=char
((window.MyNamespace.userNumber + Math.floor(Math.random() * 1e15) + new Date().getMilliseconds()).toString(36)).toUpperCase()
产生:
2DVFXJGEKL
6IZPAKFQFL
ORGOENVMG
... etc
编辑2015年6月:对于新代码,我使用shortid:https://www.npmjs.com/package/shortid
我没有使用服务器端语言,所以我不能那样做。
你肯定你不能这样做呀?
您是否忘了您正在使用Java语言(一种不断发展的语言)?
尝试SubtleCrypto
。它支持SHA-1,SHA-128,SHA-256和SHA-512哈希函数。
async function hash(message/*: string */) {
const text_encoder = new TextEncoder;
const data = text_encoder.encode(message);
const message_digest = await window.crypto.subtle.digest("SHA-512", data);
return message_digest;
} // -> ArrayBuffer
function in_hex(data/*: ArrayBuffer */) {
const octets = new Uint8Array(data);
const hex = [].map.call(octets, octet => octet.toString(16).padStart(2, "0")).join("");
return hex;
} // -> string
(async function demo() {
console.log(in_hex(await hash("Thanks for the magic.")));
})();
我参加聚会有点晚,但是您可以使用以下模块:crypto:
const crypto = require('crypto');
const SALT = '$ome$alt';
function generateHash(pass) {
return crypto.createHmac('sha256', SALT)
.update(pass)
.digest('hex');
}
此函数的结果始终是64
字符串;像这样的东西:"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"
我结合了两种解决方案(用户esmiralha和lordvlad)来获得一个函数,该函数对于支持js函数reduce()并仍与旧浏览器兼容的浏览器应该更快:
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);
} else {
var hash = 0, i, chr, len;
if (this.length == 0) return hash;
for (i = 0, len = this.length; i < len; i++) {
chr = this.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
};
例:
my_string = 'xyz';
my_string.hashCode();
如果要避免冲突,则可能需要使用安全散列,例如SHA-256。有几种JavaScript SHA-256实现。
我编写了测试以比较几种哈希实现,请参阅https://github.com/brillout/test-javascript-hash-implementations。
或访问http://brillout.github.io/test-javascript-hash-implementations/来运行测试。
这应该比其他答案更安全一些,但是在函数中,没有任何预加载的源
我基本上创建了sha1的简化版。
您获取字符串的字节并将它们按4到32位的“单词”分组,
然后将每8个单词扩展到40个单词(以对结果产生更大的影响)。
这到散列函数(最后一个减少),我们在此使用当前状态和输入进行一些数学运算。我们总是得到4个字。
这几乎是使用map,reduce ...而不是循环的单命令/单行版本,但是它仍然相当快
String.prototype.hash = function(){
var rot = (word, shift) => word << shift | word >>> (32 - shift);
return unescape(encodeURIComponent(this.valueOf())).split("").map(char =>
char.charCodeAt(0)
).reduce((done, byte, idx, arr) =>
idx % 4 == 0 ? [...done, arr.slice(idx, idx + 4)] : done
, []).reduce((done, group) =>
[...done, group[0] << 24 | group[1] << 16 | group[2] << 8 | group[3]]
, []).reduce((done, word, idx, arr) =>
idx % 8 == 0 ? [...done, arr.slice(idx, idx + 8)] : done
, []).map(group => {
while(group.length < 40)
group.push(rot(group[group.length - 2] ^ group[group.length - 5] ^ group[group.length - 8], 3));
return group;
}).flat().reduce((state, word, idx, arr) => {
var temp = ((state[0] + rot(state[1], 5) + word + idx + state[3]) & 0xffffffff) ^ state[idx % 2 == 0 ? 4 : 5](state[0], state[1], state[2]);
state[0] = rot(state[1] ^ state[2], 11);
state[1] = ~state[2] ^ rot(~state[3], 19);
state[2] = rot(~state[3], 11);
state[3] = temp;
return state;
}, [0xbd173622, 0x96d8975c, 0x3a6d1a23, 0xe5843775,
(w1, w2, w3) => (w1 & rot(w2, 5)) | (~rot(w1, 11) & w3),
(w1, w2, w3) => w1 ^ rot(w2, 5) ^ rot(w3, 11)]
).slice(0, 4).map(p =>
p >>> 0
).map(word =>
("0000000" + word.toString(16)).slice(-8)
).join("");
};
我们还将输出转换为十六进制以获取字符串而不是单词数组。
用法很简单。样品"a string".hash()
将返回"88a09e8f9cc6f8c71c4497fbb36f84cd"
我去了一个简单的串联的字符代码,转换为十六进制字符串。这有一个相对狭窄的目的,即只需要与服务器端交换SHORT字符串(例如标题,标签)的哈希表示,由于不相关的原因,就不能轻易实现已接受的hashCode Java端口。显然这里没有安全应用程序。
String.prototype.hash = function() {
var self = this, range = Array(this.length);
for(var i = 0; i < this.length; i++) {
range[i] = i;
}
return Array.prototype.map.call(range, function(i) {
return self.charCodeAt(i).toString(16);
}).join('');
}
使用Underscore可以使它更简洁,更易于浏览。例:
"Lorem Ipsum".hash()
"4c6f72656d20497073756d"
我想如果您想以类似的方式对较大的字符串进行哈希处理,则可以减少字符代码并十六进制化所得的和,而不是将各个字符连接在一起:
String.prototype.hashLarge = function() {
var self = this, range = Array(this.length);
for(var i = 0; i < this.length; i++) {
range[i] = i;
}
return Array.prototype.reduce.call(range, function(sum, i) {
return sum + self.charCodeAt(i);
}, 0).toString(16);
}
'One time, I hired a monkey to take notes for me in class. I would just sit back with my mind completely blank while the monkey scribbled on little pieces of paper. At the end of the week, the teacher said, "Class, I want you to write a paper using your notes." So I wrote a paper that said, "Hello! My name is Bingo! I like to climb on things! Can I have a banana? Eek, eek!" I got an F. When I told my mom about it, she said, "I told you, never trust a monkey!"'.hashLarge()
"9ce7"
这种方法自然会带来更多的冲突风险,尽管您可以在reduce方法中摆弄一些算术,但是您希望分散并延长哈希值。
添加此代码是因为还没有人这样做,并且似乎需要使用散列来实现它,但是它总是做得很差。
这需要一个字符串输入,以及您希望哈希值等于的最大数字,并根据字符串输入生成一个唯一的数字。
您可以使用它来生成图像数组的唯一索引(如果要为用户返回特定的化身,该化身是随机选择的,但也是根据其名称选择的,因此它将始终分配给具有该名称的人)。
当然,您也可以使用此方法将索引返回到颜色数组中,例如根据某人的名字生成唯一的头像背景颜色。
function hashInt (str, max = 1000) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash = hash & hash;
}
return Math.round(max * Math.abs(hash) / 2147483648);
}
我看不出有任何理由使用此过于复杂的密码而不是使用现成的解决方案(如对象哈希库等)。依靠供应商的生产率更高,可以节省时间并降低维护成本。
只需使用https://github.com/puleos/object-hash
var hash = require('object-hash');
hash({foo: 'bar'}) // => '67b69634f9880a282c14a0f0cb7ba20cf5d677e9'
hash([1, 2, 2.718, 3.14159]) // => '136b9b88375971dff9f1af09d7356e3e04281951'
var crypto = require('crypto');
。我认为它将在构建过程中以最小版本添加来自供应商的此依赖项代码。