Ho bisogno di convertire le stringhe in qualche forma di hash. Questo è possibile in JavaScript?
Non sto usando una lingua lato server, quindi non posso farlo in questo modo.
Ho bisogno di convertire le stringhe in qualche forma di hash. Questo è possibile in JavaScript?
Non sto usando una lingua lato server, quindi non posso farlo in questo modo.
Risposte:
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;
}
});
Fonte: http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
hash << 5 - hash
è la stessa hash * 31 + char
, ma molto più veloce. È bello perché è così veloce e 31 è un piccolo numero primo. Vinci, vinci lì.
(hash * 31) + char
è identico all'output prodotto dal codice basato su shift ((hash<<5)-hash)+char
, anche per stringhe molto lunghe (l'ho testato con stringhe contenenti oltre un milione di caratteri), quindi non è "inutilizzabile" in termini di precisione. La complessità è O (n) sia per le versioni basate sul numero che per quelle basate sul turno, quindi non è "inutilizzabile" in termini di complessità.
n
, qual è il più grande n
per il quale non posso avere una collisione?
var hashCode = function hashCode (str) {etc...}
? E poi usa come hashCode("mystring")
?
MODIFICARE
sulla base dei miei test jsperf, la risposta accettata è in realtà più veloce: http://jsperf.com/hashcodelordvlad
ORIGINALE
se qualcuno è interessato, ecco una versione migliorata (più veloce), che fallirà sui browser più vecchi che non dispongono della reduce
funzione array.
hashCode = function(s){
return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
}
versione con funzione freccia one-liner:
hashCode = s => s.split('').reduce((a,b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)
Nota: anche con il miglior hash a 32 bit, prima o poi si verificheranno delle collisioni .
La probabilità di collisione dell'hash può essere calcolata come , approssimata come ( vedi qui ). Questo può essere superiore a quanto l'intuizione suggerisce:
supponendo un hash a 32 bit e k = 10.000 elementi, si verificherà una collisione con una probabilità dell'1,2%. Per 77.163 campioni la probabilità diventa 50%! ( calcolatrice ).
Suggerisco una soluzione alternativa in fondo.
In una risposta a questa domanda
Quale algoritmo di hashing è il migliore per unicità e velocità? , Ian Boyd ha pubblicato una buona analisi approfondita . In breve (come lo interpreto), giunge alla conclusione che Murmur è il migliore, seguito da FNV-1a.
L'algoritmo String.hashCode () di Java proposto da esmiralha sembra essere una variante di DJB2.
Alcuni benchmark con stringhe di input di grandi dimensioni qui: http://jsperf.com/32-bit-hash
Quando si eseguono l'hash delle stringhe di input brevi , le prestazioni del mormorio diminuiscono rispetto a DJ2B e FNV-1a: http://jsperf.com/32- bit-hash / 3
Quindi, in generale, consiglierei murmur3.
Vedi qui per un'implementazione JavaScript:
https://github.com/garycourt/murmurhash-js
Se le stringhe di input sono brevi e le prestazioni sono più importanti della qualità di distribuzione, utilizzare DJB2 (come proposto dalla risposta accettata da esmiralha).
Se la qualità e le dimensioni ridotte del codice sono più importanti della velocità, utilizzo questa implementazione di FNV-1a (basata su questo codice ).
/**
* 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;
}
Migliora la probabilità di collisione
Come spiegato qui , possiamo estendere la dimensione del bit di hash usando questo trucco:
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)
}
Usalo con cura e non aspettarti troppo però.
("0000000" + (hval >>> 0).toString(16)).substr(-8);
? Non è lo stesso di (hval >>> 0).toString(16)
?
hval
, (hval >>> 0).toString(16)
potrebbe contenere meno di 8 caratteri, quindi puoi riempirlo di zeri. Ero solo confuso perché mi è (hval >>> 0).toString(16)
sempre risultato esattamente una stringa di 8 caratteri.
Math.imul
funzione ES6 . Questo da solo lo rende il migliore punto di riferimento, e alla fine una scelta migliore rispetto a DJB2 a lungo termine.
Basato sulla risposta accettata in ES6. Più piccolo, gestibile e funziona nei browser moderni.
function hashCode(str) {
return str.split('').reduce((prevHash, currVal) =>
(((prevHash << 5) - prevHash) + currVal.charCodeAt(0))|0, 0);
}
// Test
console.log("hashCode(\"Hello!\"): ", hashCode('Hello!'));
EDIT (2019-11-04) :
versione con funzione freccia one-liner:
const hashCode = s => s.split('').reduce((a,b) => (((a << 5) - a) + b.charCodeAt(0))|0, 0)
// test
console.log(hashCode('Hello!'))
str += ""
prima dell'hash per evitare l'eccezione str.split is not a function
generata quando le non stringhe venivano passate come parametri
hash |= 0
per convertire in un int a 32 bit. Questa implementazione no. è un insetto?
Quasi la metà delle risposte sono implementazioni di Java
String.hashCode
, che non è né di alta qualità né super veloce. Non è niente di speciale, si moltiplica solo per 31 per ogni personaggio. Può essere implementato in modo semplice ed efficiente in una riga ed è molto più veloce conMath.imul
:
hashCode=s=>{for(var i=0,h;i<s.length;i++)h=Math.imul(31,h)+s.charCodeAt(i)|0;return h}
Detto questo, ecco qualcosa di meglio : cyrb53 , un hash a 53 bit semplice ma di alta qualità. È abbastanza veloce, offre un'ottima distribuzione dell'hash e ha tassi di collisione significativamente più bassi rispetto a qualsiasi hash a 32 bit.
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);
};
Simile ai noti algoritmi MurmurHash / xxHash, utilizza una combinazione di moltiplicazione e Xorshift per generare l'hash, ma non altrettanto approfondito. Di conseguenza è più veloce di entrambi in JavaScript e significativamente più semplice da implementare.
Raggiunge una valanga (non rigorosa), il che significa sostanzialmente che piccoli cambiamenti nell'input hanno grandi cambiamenti nell'output, rendendo l'hash risultante casuale:
0xc2ba782c97901 = cyrb53("a")
0xeda5bc254d2bf = cyrb53("b")
0xe64cc3b748385 = cyrb53("revenge")
0xd85148d13f93a = cyrb53("revenue")
Puoi anche fornire un seme per flussi alternativi dello stesso input:
0xee5e6598ccd5c = cyrb53("revenue", 1)
0x72e2831253862 = cyrb53("revenue", 2)
0x0de31708e6ab7 = cyrb53("revenue", 3)
Tecnicamente è un hash a 64 bit (due hash non correlati in parallelo a 32 bit), ma JavaScript è limitato a numeri interi a 53 bit. Se necessario, è ancora possibile utilizzare l'output completo a 64 bit modificando la riga di ritorno per una stringa esadecimale o un array.
Tenere presente che la costruzione di stringhe esadecimali può rallentare drasticamente l'elaborazione batch in situazioni critiche per le prestazioni.
return (h2>>>0).toString(16).padStart(8,0)+(h1>>>0).toString(16).padStart(8,0);
// or
return [h2>>>0, h1>>>0];
E solo per divertimento, ecco un hash minimo a 32 bit in 89 caratteri con qualità superiore rispetto a FNV o 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
inizializzato?
'imul'
.
Se aiuta qualcuno, ho combinato le prime due risposte in una versione più vecchia tollerante al browser, che utilizza la versione veloce se reduce
è disponibile e ricade nella soluzione di esmiralha se non lo è.
/**
* @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;
}
L'uso è come:
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; }
Questa è una variante raffinata e dalle prestazioni migliori:
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;
};
Ciò corrisponde all'implementazione di Java dello standard object.hashCode()
Eccone anche uno che restituisce solo hashcode positivi:
String.prototype.hashcode = function() {
return (this.hashCode() + 2147483647) + 1;
};
Ed eccone uno corrispondente per Java che restituisce solo hashcode positivi:
public static long hashcode(Object obj) {
return ((long) obj.hashCode()) + Integer.MAX_VALUE + 1l;
}
Godere!
Sono un po 'sorpreso che nessuno abbia ancora parlato della nuova API di SubtleCrypto .
Per ottenere un hash da una stringa, è possibile utilizzare il subtle.digest
metodo:
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
non è esattamente performante.
Grazie all'esempio di mar10, ho trovato il modo di ottenere gli stessi risultati in C # E Javascript per un FNV-1a. Se sono presenti caratteri unicode, la parte superiore viene scartata per motivi di prestazioni. Non so perché sarebbe utile mantenerli quando si esegue l'hashing, poiché per ora sono solo percorsi URL di hashing.
Versione 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());
}
Versione 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);
}
Math.imul
può essere utilizzato per la fase di moltiplicazione, che migliora notevolmente le prestazioni . L'unico problema è che non funzionerà in IE11 senza uno spessore .
Uno veloce e conciso che è stato adattato da qui :
String.prototype.hashCode = function() {
var hash = 5381, i = this.length
while(i)
hash = (hash * 33) ^ this.charCodeAt(--i)
return hash >>> 0;
}
Avevo bisogno di una funzione simile (ma diversa) per generare un ID univoco in base al nome utente e all'ora corrente. Così:
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()
produce:
2DVFXJGEKL
6IZPAKFQFL
ORGOENVMG
... etc
modifica giugno 2015: per il nuovo codice che uso shortid: https://www.npmjs.com/package/shortid
La mia veloce (molto lunga) fodera basata sul Multiply+Xor
metodo di FNV :
my_string.split('').map(v=>v.charCodeAt(0)).reduce((a,v)=>a+((a<<7)+(a<<3))^v).toString(16);
Non sto usando una lingua lato server, quindi non posso farlo in questo modo.
Sei sicuro di non poterlo fare in quel modo ?
Hai dimenticato di utilizzare Javascript, la lingua in continua evoluzione?
Prova SubtleCrypto
. Supporta le funzioni hash SHA-1, SHA-128, SHA-256 e 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.")));
})();
Sono in ritardo alla festa, ma puoi usare questo modulo: crypto :
const crypto = require('crypto');
const SALT = '$ome$alt';
function generateHash(pass) {
return crypto.createHmac('sha256', SALT)
.update(pass)
.digest('hex');
}
Il risultato di questa funzione è sempre la 64
stringa di caratteri; qualcosa come questo:"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"
Ho combinato le due soluzioni (utenti esmiralha e lordvlad) per ottenere una funzione che dovrebbe essere più veloce per i browser che supportano la funzione js di riduzione () e comunque compatibile con i vecchi browser:
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;
}
};
Esempio:
my_string = 'xyz';
my_string.hashCode();
Se si desidera evitare le collisioni, è possibile utilizzare un hash sicuro come SHA-256 . Esistono diverse implementazioni JavaScript SHA-256.
Ho scritto dei test per confrontare diverse implementazioni di hash, vedi https://github.com/brillout/test-javascript-hash-implementations .
Oppure vai su http://brillout.github.io/test-javascript-hash-implementations/ , per eseguire i test.
Questo dovrebbe essere un hash un po 'più sicuro rispetto ad altre risposte, ma in una funzione, senza alcuna fonte precaricata
Ho creato fondamentalmente una versione semplificata minimizzata di sha1.
Prendi i byte della stringa e li raggruppa per "parole" da 4 a 32 bit.
Quindi estendiamo ogni 8 parole a 40 parole (per un impatto maggiore sul risultato).
Questo va alla funzione di hashing (l'ultima riduzione) dove facciamo alcuni calcoli con lo stato corrente e l'input. Otteniamo sempre 4 parole.
Questa è quasi una versione a un comando / una riga che usa map, riduci ... invece dei loop, ma è ancora piuttosto veloce
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("");
};
convertiamo anche l'output in esadecimale per ottenere una stringa anziché una matrice di parole.
L'utilizzo è semplice. per l'esempio "a string".hash()
tornerà"88a09e8f9cc6f8c71c4497fbb36f84cd"
Ho optato per una semplice concatenazione di codici char convertiti in stringhe esadecimali. Questo ha uno scopo relativamente ristretto, vale a dire solo la necessità di una rappresentazione hash di una stringa SHORT (ad esempio titoli, tag) da scambiare con un lato server che per motivi non rilevanti non può facilmente implementare la porta Java hashCode accettata. Ovviamente nessuna applicazione di sicurezza qui.
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('');
}
Questo può essere reso più conciso e tollerante con il browser con Underscore. Esempio:
"Lorem Ipsum".hash()
"4c6f72656d20497073756d"
Suppongo che se si desidera eseguire l'hashing di stringhe più grandi in modo simile, è possibile ridurre i codici char ed esadecimare la somma risultante anziché concatenare i singoli caratteri insieme:
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"
Naturalmente più rischio di collisione con questo metodo, anche se si potrebbe giocherellare con l'aritmetica nella riduzione, tuttavia si voleva diversificare e allungare l'hash.
Versione leggermente semplificata della risposta di @ esmiralha.
Non sostituisco String in questa versione, poiché ciò potrebbe comportare comportamenti indesiderati.
function hashCode(str) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash = ~~(((hash << 5) - hash) + str.charCodeAt(i));
}
return hash;
}
Aggiungendo questo perché nessuno lo ha ancora fatto, e questo sembra essere richiesto e implementato molto con gli hash, ma è sempre fatto molto male ...
Ciò richiede un input di stringa e un numero massimo a cui si desidera che l'hash sia uguale e produce un numero univoco basato sull'input di stringa.
Puoi usarlo per produrre un indice univoco in una matrice di immagini (Se vuoi restituire un avatar specifico per un utente, scelto a caso, ma anche scelto in base al loro nome, quindi sarà sempre assegnato a qualcuno con quel nome ).
Puoi anche usare questo, ovviamente, per restituire un indice in una matrice di colori, come per generare colori di sfondo avatar unici basati sul nome di qualcuno.
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);
}
Non vedo alcun motivo per utilizzare questo codice crittografico complicato invece di soluzioni pronte all'uso, come la libreria di hash degli oggetti, ecc. Fare affidamento sul fornitore è più produttivo, consente di risparmiare tempo e ridurre i costi di manutenzione.
Basta usare 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');
. Penso che aggiunge questo codice di dipendenza dal fornitore nella versione ridotta durante una compilazione.