从Java脚本中的字符串生成哈希

2020/09/23 06:21 · javascript ·  · 0评论

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

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

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/

编辑

根据我的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位哈希,冲突迟早也会发生。

哈希冲突概率可以计算为
1-e ^(-k(k-1)/ 2N,近似为
k ^ 2 / 2N
参见此处)。这可能比直觉所暗示的要高:

假设一个32位哈希且k = 10,000个项目,则发生碰撞的概率为1.2%。
对于77,163个样本,概率变为50%!
计算器)。

我建议在底部的一种解决方法。

在回答这个问题时,
哪种哈希算法最适合唯一性和速度?,伊恩·博伊德(Ian Boyd)发表了深入的分析简而言之(据我的解释),他得出的结论是Murmur最好,其次是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-散列/ 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!'))

几乎一半的答案是Java的实现,String.hashCode既不是高质量的也不是超快的。这是1970年代的技术-只是将字符串中的每个字符乘以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];

请注意,在性能至关重要的情况下,构造十六进制字符串会大大减慢批处理的速度。数组效率更高,但显然不如Number直观String


只是为了好玩,这是我能想到的最小的哈希值,但仍然很不错。它是最小的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}

这是一种精致且性能更好的变体:

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

请享用!

没有原型:

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

如果对任何人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();

令我惊讶的是,还没有人谈论新的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);
  });

感谢mar10的示例,我找到了一种在FNV-1a的C#AND 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);
}

一种快速简洁的方法,从这里改编而成

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

我基于FNV Multiply+Xor方法的快速(很长)衬垫

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

微妙的加密摘要

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

你肯定你不能这样做

您是否忘了您正在使用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.")));
})();

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

我参加聚会有点晚,但是您可以使用以下模块: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方法中摆弄一些算术,但是您希望分散并延长哈希值。

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

添加此代码是因为还没有人这样做,并且似乎需要使用哈希值来实现它,但是它总是做得很差。

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

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

当然,您也可以使用此方法将索引返回到颜色数组中,例如根据某人的名字生成唯一的头像背景颜色。

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

这会根据传入的任意数量的参数生成一致的哈希值:

/**
 * 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"

我看不出有任何理由使用这种过于复杂的密码而不是使用现成的解决方案(例如对象哈希库等)。依靠供应商可以提高生产率,节省时间并降低维护成本。

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

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

hash({foo: 'bar'}) // => '67b69634f9880a282c14a0f0cb7ba20cf5d677e9'
hash([1, 2, 2.718, 3.14159]) // => '136b9b88375971dff9f1af09d7356e3e04281951'
本文地址:http://javascript.askforanswer.com/congjavajiaobenzhongdezifuchuanshengchenghaxi.html
文章标签: ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!