web-dev-qa-db-ja.com

Javascriptの文字列からハッシュを生成する

文字列を何らかの形のハッシュに変換する必要があります。これはJavaScriptで可能ですか?

私はサーバーサイドの言語を利用していないので、そのようにはできません。

469
Freesnöw
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/ /

683
esmiralha

_編集_

私の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);              
}
109
lordvlad

注:最高の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
short入力文字列がハッシュされると、DJ2BおよびFNV-1aと比較して、murmurのパフォーマンスが低下します。 http://jsperf.com/32-ビットハッシュ/

だから、一般的には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)
}

慎重に使用し、あまり期待しないでください。

86
mar10

承認された回答に基づく 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!'));
44
deekshith

誰かを助けてくれるなら、私はreduceが利用可能なら高速版を使い、そうでなければesmiralhaの解決法に頼る、より古いブラウザ耐性のある版に上位2つの答えを組み合わせました。

/**
 * @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 = new String("some string to be hashed").hashCode();
24
Kyle Falconer

これは洗練された、よりパフォーマンスの良い変形です。

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

これは標準のobject.hashCode()のJavaの実装と一致します

正のハッシュコードのみを返すものもあります。

String.prototype.hashcode = function() {
    return (this.hashCode() + 2147483647) + 1;
};

そしてこれは、正のハッシュコードのみを返す、Java用のものです。

public static long hashcode(Object obj) {
    return ((long) obj.hashCode()) + Integer.MAX_VALUE + 1l;
}

楽しい!

19
momomo

新しい 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);
  });
14
Kaiido

これはシンプルで、よく分散された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);
};

それはxxHash/MurmurHash3に似たテクニックを使いますが、徹底的ではありません。それはなだれを起こさない(厳密ではない)ので、入力の小さな変化は出力の大きな変化を持ち、それはランダムに見える。

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ビットハッシュですが、JavaScriptは53ビット整数に制限されています。 16進数の文字列または配列の戻り行を変更することで、64ビットすべてを使用できます。

return (h2>>>0).toString(16).padStart(8,0)+(h1>>>0).toString(16).padStart(8,0);
// or
return [h2>>>0, h1>>>0];

問題は、16進数の文字列を作成することがパフォーマンス上のボトルネックになり、配列には1つではなく2つの比較演算子が必要になることです。高性能アプリケーションに使用する場合は、この点に留意してください。


そして、楽しみのために、これはまだFNV/DJB2/SMDBに勝る89文字の最小32ビットハッシュです:

TSH=s=>{for(var i=0,h=6;i<s.length;)h=Math.imul(h^s.charCodeAt(i++),9**9);return h^h>>>9}
13
bryc

Mar10による例のおかげで、私はFNV-1aのC#AND Javascriptで同じ結果を得る方法を見つけました。ユニコード文字が存在する場合、上部はパフォーマンス上の理由で破棄されます。現在のところ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);
}
6
djabraham

FNVのMultiply+Xorメソッドを基にした私の手っ取り早い(非常に長い)1つのライナー:

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

here :から適応された速くて簡潔なもの

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

ユーザー名と現在時刻に基づいて一意の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

4
jcollum

衝突を避けたいのなら、 secure hash like SHA-256 を使うといいでしょう。いくつかのJavaScript SHA-256実装があります。

私はいくつかのハッシュ実装を比較するためのテストを書きました。 https://github.com/brillout/test-javascript-hash-implementations を見てください。

または http://brillout.github.io/test-javascript-hash-implementations/ にアクセスしてテストを実行します。

2
brillout

私は2つの解決法(ユーザー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();
2
Frank

私はちょっとパーティーに遅刻しますが、あなたはこのモジュールを使うことができます: crypto

const crypto = require('crypto');

const SALT = '$ome$alt';

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

この関数の結果は常に64文字列です。このようなもの:"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"

2
Frismaury

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

SubtleCrypto.digest

サーバー側の言語を利用していないので、そのようにすることはできません。

できませんか?そのように

進化し続けるJavascriptを使用していることを忘れましたか?

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.")));
})();

16進数の文字列に変換されたcharコードの単純な連結に行きました。これは比較的狭い目的に役立ちます。つまり、関係のない理由で受け入れられた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"

同様の方法でより大きな文字列をハッシュしたい場合は、個々の文字を連結するのではなく、文字コードを減らして合計を16進数にすることができます。

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の算術演算をいじることはできますが、ハッシュを多様化して長くしたいと思いました。

1
swornabsent

まだ誰もやっていないのでこれを付け加えてください、そしてこれはハッシュで多くを求められそして実行されるように思われます、しかしそれは常に非常に不十分に行われます...

これは、文字列入力と、ハッシュに等しくしたい最大数を取り、文字列入力に基づいて一意の数値を生成します。

これを使用して、画像の配列に一意のインデックスを作成できます(ランダムに選択されただけでなく、その名前に基づいて選択されたユーザーの特定のアバターを返したい場合は、常にその名前のユーザーに割り当てられます) ).

もちろん、これを使用して、インデックスを色の配列に戻すこともできます。たとえば、他の人の名前に基づいて固有のアバターの背景色を生成する場合などです。

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);
}
1
Nick Steele