我需要将字符串转换为某种形式的哈希。这在JavaScript中是可能的吗?

我没有使用服务器端语言,所以我不能这样做。


String.prototype.hashCode=函数(){var散列=0,i、 chr;如果(this.length==0)返回哈希;对于(i=0;i<this.length;i++){chr=this.charCodeAt(i);哈希=((哈希<<5)-哈希)+chr;哈希|=0;//转换为32位整数}返回哈希;}const str='收入'console.log(str,str.hashCode())

来源


EDIT

根据我的jsperf测试,公认的答案实际上更快:http://jsperf.com/hashcodelordvlad

原始的,原始的

如果有人感兴趣,这里有一个改进的(更快的)版本,它将在缺少reduce数组功能的旧浏览器上失败。

hashCode=函数{return s.split(“”).reduce(函数(a,b){a=((a<<5)-a)+b.charCodeAt(0);返回a&a;}, 0);}//测试console.log(hashCode(“hello”));console.log(hashCode(“这是一个文本。”));console.log(hashCode(“Luis Fonsi的Despacito”));

单线箭头功能版本:

hashCode=s=>s.split(“”).reduce((a,b)=>{a=((a<<5)-a)+b.charCodeAt(0);返回a&a},0)//测试console.log(hashCode(“hello”));console.log(hashCode(“这是一个文本。”));console.log(hashCode(“Luis Fonsi的Despacito”));


我需要一个类似的函数(但不同)来根据用户名和当前时间生成一个唯一的ish 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 

编辑2022年7月:正如@canRau指出的那样,shortid的作者现在更喜欢nanoidhttps://github.com/ai/nanoid/


如果这对任何人都有帮助的话,我将前两个答案组合成一个更老的浏览器容忍版本,如果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();

注意:即使使用最好的32位哈希,冲突也迟早会发生。哈希冲突概率可以计算为,近似为(参见此处)。这可能比直觉所暗示的更高:假设32位哈希和k=10000个项目,则发生冲突的概率为1.2%。77163个样本的概率为50%!(计算器)。我建议在底部使用变通方法。

在回答这个问题时哪种哈希算法最适合唯一性和速度?,伊恩·博伊德发表了一篇很好的深入分析。简而言之(正如我所解释的那样),他得出的结论是MurmurHash是最好的,其次是FNV-1a。esmiralha提出的Java String.hashCode()算法似乎是DJB2的变体。

FNV-1a的分布比DJB2更好,但速度较慢DJB2比FNV-1a更快,但倾向于产生更多的碰撞MurmurHash3比DJB2和FNV-1a更好更快(但优化的实现需要比FNV和DJB2更多的代码行)

这里有一些输入字符串较大的基准测试:http://jsperf.com/32-bit-hash当对短输入字符串进行散列处理时,相对于DJ2B和FNV-1a,杂音的性能会下降:http://jsperf.com/32-bit-hash/3

因此,总的来说,我会推荐杂音3。请参阅此处了解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)
}

小心使用,但不要期望太多。


我将这两种解决方案(用户esmiralha和lordflad)结合在一起,得到了一个对于支持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();

得益于mar10的示例,我找到了一种在C#和Javascript中为FNV-1a获得相同结果的方法。如果存在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);
}

我尝试了将字符代码转换为十六进制字符串的简单串联。这有一个相对狭窄的目的,即只需要与服务器端交换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('');
}

这可以通过Undercore变得更加简洁和浏览器宽容。例子:

"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中摆弄算法,但您希望多样化并延长哈希。


如果您想避免冲突,您可能需要使用SHA-256这样的安全散列。有几个JavaScript SHA-256实现。

我编写了测试来比较几个哈希实现,请参见https://github.com/brillout/test-javascript-hash-implementations.

或转到http://brillout.github.io/test-javascript-hash-implementations/,以运行测试。


这是一个改进的、性能更好的变体,与Java对CharSequence的标准object.hashCode()的实现相匹配。

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;
}

对于那些不想将其附加到字符串的人,没有原型:

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;
}

享受


基于ES6中接受的答案。更小,可维护,可在现代浏览器中使用。

函数hashCode(str){return str.split(“”).reduce((prevHash,currVal)=>(((prevHash<<5)-previvHash)+currVal.charCodeAt(0))|0,0);}//测试console.log(“hashCode(\“Hello!\”):“,hashCode('Hello!'));

编辑(2019-11-04):

单线箭头功能版本:

consthashCode=s=>s.split(“”).reduce((a,b)=>(((a<<5)-a)+b.charCodeAt(0))|0,0)//测试console.log(hashCode(“您好!”))


@esmiralha答案的略微简化版本。

我不会在这个版本中重写String,因为这可能会导致一些不希望的行为。

function hashCode(str) {
    var hash = 0;
    for (var i = 0; i < str.length; i++) {
        hash = ~~(((hash << 5) - hash) + str.charCodeAt(i));
    }
    return hash;
}

这是一个快速而简洁的版本,改编自这里:

String.prototype.hashCode = function() {
  var hash = 5381, i = this.length
  while(i)
    hash = (hash * 33) ^ this.charCodeAt(--i)
  return hash >>> 0;
}

我有点惊讶,还没有人谈论新的SubtleCryptoAPI。

要从字符串中获取哈希,可以使用suble.desumest方法:

函数getHash(str,algo=“SHA-256”){let strBuf=newTextEncoder().encode(str);return crypto.define.digest(算法,strBuf).then(哈希=>{window.hash=哈希;//这里hash是arrayBuffer,//所以我们将其转换为十六进制版本let result=“”;const view=新数据视图(哈希);for(设i=0;i<hash.byteLength;i+=4){result+=('000000000'+view.getUint32(i).toString(16)).sslice(-8);}返回结果;});}getHash('hello world').then(哈希=>{console.log(哈希);});


我基于FNV的乘法+Xor方法的快速(非常长)一行:

my_string.split('').map(v=>v.charCodeAt(0)).reduce((a,v)=>a+((a<<7)+(a<<3))^v).toString(16);

我参加派对有点晚了,但你可以使用这个模块:加密:

const crypto = require('crypto');

const SALT = '$ome$alt';

function generateHash(pass) {
  return crypto.createHmac('sha256', SALT)
    .update(pass)
    .digest('hex');
}

此函数的结果始终是64个字符的字符串;类似于:“aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a”


这里的许多答案都是取自Java的String.hashCode哈希函数。它可以追溯到1981年的Gosling Emacs,它非常脆弱,在现代JavaScript中表现得毫无意义。事实上,通过使用ES6 Math.imul,实现速度可能会大大加快,但没有人注意到。我们可以在基本相同的性能下做得更好。

这是我做的一个cryb53,一个简单但高质量的53位散列。它非常快,提供了非常好的*哈希分布,并且因为它输出53位,所以与任何32位哈希相比,具有明显更低的冲突率。此外,您可以忽略SA的CC许可证,因为它是我GitHub上的公共域。

常量cyrb53=(str,种子=0)=>{设h1=0xdeadbeef^种子,h2=0x41c6ce57^种子;for(设i=0,ch;i<str.length;i++){ch=str.charCodeAt(i);h1=数学模拟(h1^ch,2654435761);h2=数学模拟(h2^ch,1597334677);}h1=数学模拟(h1^(h1>>16),2246822507)^数学模拟(h2^(h2>>13),3266489909);h2=数学模拟(h2^(h2>>16),2246822507)^数学模拟(h1^(h1>>13),3266489909);返回4294967296*(2097151&h2)+(h1>>>0);};console.log(`cyrb53('a')->${cyrb53“'a')}`)console.log(`cyrb53('b')->${cyrb53console.log(`cyrb53('return')->${cyrb53('reten')}`)console.log(`cyrb53('resident')->${cyrb53console.log(`cyrb53('resident',1)->${cyrb53console.log(`cyrb53('resident',2)->${cyrb53console.log(`cyrb53('resident',3)->${cyrb53

*它大致类似于众所周知的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或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}

我看不出有任何理由使用这种过于复杂的加密代码,而不是现成的解决方案,如对象哈希库等。依赖供应商更高效、节省时间并降低维护成本。

只需使用https://github.com/puleos/object-hash

var hash = require('object-hash');

hash({foo: 'bar'}) // => '67b69634f9880a282c14a0f0cb7ba20cf5d677e9'
hash([1, 2, 2.718, 3.14159]) // => '136b9b88375971dff9f1af09d7356e3e04281951'

添加这一点是因为还没有人这样做,而这似乎是用哈希来要求和实现的,但它总是做得很糟糕。。。

这需要一个字符串输入和您希望哈希值相等的最大值,并根据字符串输入生成一个唯一的数字。

您可以使用它来生成图像数组中的唯一索引(如果您希望为用户返回一个特定的化身,该化身是随机选择的,但也是基于其名称选择的,因此它将始终分配给具有该名称的人)。

当然,您也可以使用它将索引返回到颜色数组中,例如根据某人的姓名生成独特的化身背景颜色。

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);
}

子级Crypto.digest

我没有使用服务器端语言,所以我不能这样做。

你确定你不能那样做吗?

你忘了你在使用Javascript吗?

尝试SubleCrypto。它支持SHA-1、SHA-128、SHA-256和SHA-512哈希函数。


异步函数哈希(message/*:string*/){const text_encoder=新的TextEncoder;常量数据=text_encoder.encode(消息);const message_digest=等待window.crypto.intege.digest(“SHA-512”,数据);返回message_digest;}//->阵列缓冲区函数in_hex(data/*:ArrayBuffer*/){const八位字节=新的Uint8Array(数据);const hex=[].map.call(八位字节,八位字节=>八位字节.toString(16).padStart(2,“0”)).join(“”);返回十六进制;}//->字符串(异步函数demo(){console.log(in_hex(等待哈希(“感谢魔法”)));})();


这将基于传入的任意数量的参数生成一致的哈希:

/**
 * Generates a hash from params passed in
 * @returns {string} hash based on params
 */
function fastHashParams() {
    var args = Array.prototype.slice.call(arguments).join('|');
    var hash = 0;
    if (args.length == 0) {
        return hash;
    }
    for (var i = 0; i < args.length; i++) {
        var char = args.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash = hash & hash; // Convert to 32bit integer
    }
    return String(hash);
}

fastHashParams('hello world')输出“990433808”

fastHashParams('this',1,'has','lots','of','params',true)输出“1465480334”


这里是一个紧凑的ES6友好可读片段

const stringHashCode = str => {
  let hash = 0
  for (let i = 0; i < str.length; ++i)
    hash = Math.imul(31, hash) + str.charCodeAt(i)

  return hash | 0
}

UUID v3和UUID v5实际上是给定输入字符串的散列。

UUID v3基于MD5,UUID v5基于SHA-1。

因此,最明显的选择是使用UUIDv5。

幸运的是,有一个流行的npm包,其中包括所有UUID算法。

npm install uuid

要实际生成UUIDv5,您需要一个唯一的命名空间。这个名称空间就像种子,应该是常量,以确保给定输入的输出始终相同。具有讽刺意味的是,您应该生成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);

詹金斯一次一哈希非常好:

//Credits (modified code): Bob Jenkins (http://www.burtleburtle.net/bob/hash/doobs.html)
//See also: https://en.wikipedia.org/wiki/Jenkins_hash_function
//Takes a string of any size and returns an avalanching hash string of 8 hex characters.
function jenkinsOneAtATimeHash(keyString)
{
  let hash = 0;
  for (charIndex = 0; charIndex < keyString.length; ++charIndex)
  {
    hash += keyString.charCodeAt(charIndex);
    hash += hash << 10;
    hash ^= hash >> 6;
  }
  hash += hash << 3;
  hash ^= hash >> 11;
  //4,294,967,295 is FFFFFFFF, the maximum 32 bit unsigned integer value, used here as a mask.
  return (((hash + (hash << 15)) & 4294967295) >>> 0).toString(16)
};

示例:

jenkinsOneAtATimeHash('test')
"31c25ec1"
jenkinsOneAtATimeHash('a')
"ca2e9442"
jenkinsOneAtATimeHash('0')
"6e3c5c6b"

您还可以删除末尾的.toString(16)部分以生成数字:

jenkinsOneAtATimeHash2('test')
834821825
jenkinsOneAtATimeHash2('a')
3392050242
jenkinsOneAtATimeHash2('0')
1849449579

请注意,如果您不需要对字符串或键进行哈希,而只需要凭空生成哈希,则可以使用:

window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16)

示例:

window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16)
"6ba9ea7"
window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16)
"13fe7edf"
window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16)
"971ffed4"

与上面相同,您可以删除末尾的`.toString(16)部分以生成数字:

window.crypto.getRandomValues(new Uint32Array(1))[0]
1154752776
window.crypto.getRandomValues(new Uint32Array(1))[0]
3420298692
window.crypto.getRandomValues(new Uint32Array(1))[0]
1781389127

注意:您也可以使用此方法一次生成多个值,例如:

window.crypto.getRandomValues(new Uint32Array(3))
Uint32Array(3) [ 2050530949, 3280127172, 3001752815 ]

函数hashCode(str){return str.split(“”).reduce((prevHash,currVal)=>(((prevHash<<5)-previvHash)+currVal.charCodeAt(0))|0,0);}//测试console.log(“hashCode(\“Hello!\”):“,hashCode('Hello!'));