Мені потрібно конвертувати рядки в якусь форму хешу. Чи можливо це в JavaScript?
Я не використовую серверну мову, тому не можу це зробити так.
Мені потрібно конвертувати рядки в якусь форму хешу. Чи можливо це в 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/
hash << 5 - hash
те саме, що hash * 31 + char
і ЛОТ швидше. Це приємно, бо це так швидко, а 31 - це невеликий прем'єр. Win виграти там.
(hash * 31) + char
, ідентичний виводу, отриманому кодом на основі зсуву ((hash<<5)-hash)+char
, навіть для дуже довгих рядків (я тестував його з рядками, що містять понад мільйон символів), тому він не "непридатний" з точки зору точності. Складність становить O (n) як для чисельних, так і для зрушень на основі змін, тому вона не є "непридатною" з точки зору складності.
n
, яка найбільша, n
за яку я не можу зіткнутися?
var hashCode = function hashCode (str) {etc...}
? А потім використовувати як hashCode("mystring")
?
EDIT
На основі моїх тестів на 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 = 10000 предметів, відбудеться зіткнення з ймовірністю 1,2%. Для 77 163 проби ймовірність стає 50%! ( калькулятор ).
Я пропоную вирішити внизу.
У відповідь на це питання
Який алгоритм хешування найкращий для унікальності та швидкості? , Ян Бойд опублікував хороший глибокий аналіз . Якщо коротко (як я його тлумачу), він приходить до висновку, що Мурмур найкращий, за ним слідує FNV-1a.
Алгоритм Java String.hashCode (), запропонований esmiralha, є варіантом 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
. Це одне лише робить його провідними орієнтирами, і, зрештою, кращим вибором, ніж 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!'));
EDIT (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;
}
Насолоджуйтесь!
Я трохи здивований, поки ніхто не говорив про новий API SubtleCrypto .
Щоб отримати хеш із рядка, ви можете використовувати 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, я знайшов спосіб отримати ті самі результати у C # AND 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);
}
Math.imul
його можна використовувати для кроку множення, що значно покращує продуктивність . Тільки проблема в тому, що не працюватиме в IE11 без прокладки .
Швидкий і стислий, який був адаптований звідси :
String.prototype.hashCode = function() {
var hash = 5381, i = this.length
while(i)
hash = (hash * 33) ^ this.charCodeAt(--i)
return hash >>> 0;
}
Мені потрібна була аналогічна функція (але інша), щоб створити унікальний ідентифікатор іш на основі імені користувача та поточного часу. Тому:
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: Для нового коду я використовую shorttid: https://www.npmjs.com/package/shortid
Я не використовую серверну мову, тому не можу це зробити так.
Ви впевнені , що ви не можете зробити це таким чином ?
Ви забули, що використовуєте 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.")));
})();
Я якось спізнююся на вечірку, але ви можете використовувати цей модуль: 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 скарати () і все ще сумісна зі старими браузерами:
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, зменшити ... замість циклів, але це все ще досить швидко
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("");
};
ми також перетворюємо вихід у шістнадцятковий, щоб отримати рядок замість масиву слів.
Використання просте. для expample "a string".hash()
повернеться"88a09e8f9cc6f8c71c4497fbb36f84cd"
Я пішов на просте з'єднання кодів char, перетворених у шістнадцяткові рядки. Це служить відносно вузькій цілі, а саме просто потрібне хеш-представлення рядка 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('');
}
Це можна зробити більш короткою та стійкою до браузера за допомогою підкреслення. Приклад:
"Lorem Ipsum".hash()
"4c6f72656d20497073756d"
Я гадаю, що якщо ви хочете сховати хеш-файли більших рядків, ви можете просто зменшити коди char та збільшити отриману суму, а не об'єднати окремих символів разом:
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"
Природно, більший ризик зіткнення з цим методом, хоча ви можете пограбувати з арифметикою у зменшенні, проте ви хотіли диверсифікувати та подовжити хеш.
Додайте це тому, що ще ніхто цього не зробив, і це, здається, просять і реалізують багато з хешами, але це завжди робиться дуже погано ...
Це вимагає введення рядка і максимального числа, за яким ви хочете, щоб хеш дорівнював, і створює унікальне число на основі введення рядка.
Ви можете використовувати це для створення унікального індексу в масиві зображень (Якщо ви хочете повернути певний аватар для користувача, вибраний випадковим чином, але також обраний на основі їх імені, тому він завжди буде призначений комусь із цим ім'ям ).
Ви можете, звичайно, також використовувати це для повернення індексу в масив кольорів, як-от для створення унікальних кольорів тла аватара на основі чийогось імені.
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');
. Я думаю, що він додає цей код залежності від постачальника у мінімізованій версії під час збирання.