Мне нужно преобразовать строки в какую-то форму хэша. Это возможно в 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 - это простое число. Выиграй, выиграй там.
(hash * 31) + char
идентичен выводу, сгенерированному кодом на основе сдвига ((hash<<5)-hash)+char
, даже для очень длинных строк (я тестировал его со строками, содержащими более миллиона символов), поэтому он не является «непригодным» в терминах точности. Сложность составляет O (n) для версий на основе числа и на основе сдвига, поэтому она не является «непригодной» с точки зрения сложности.
n
, что является самым большим, n
для которого я не могу иметь коллизию?
var hashCode = function hashCode (str) {etc...}
? А потом использовать как hashCode("mystring")
?
РЕДАКТИРОВАТЬ
основываясь на моих тестах 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%! ( калькулятор ).
Я предлагаю обходной путь внизу.
В ответ на этот вопрос
Какой алгоритм хеширования лучше всего подходит для уникальности и скорости? Ян Бойд опубликовал хороший углубленный анализ . Короче говоря (насколько я понимаю), он приходит к выводу, что Murmur лучше, а затем FNV-1a.
Алгоритм Java String.hashCode (), предложенный Эсмиралхой, кажется вариантом 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
функции ES6 . Это само по себе делает его высшим ориентиром и, в конечном счете, лучшим выбором, чем 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!'));
РЕДАКТИРОВАТЬ (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-битное целое число. Эта реализация не делает. Это ошибка?
Почти половина ответов - реализация 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
не совсем производительная.
Благодаря примеру к 10 марта, я нашел способ получить те же результаты в C # AND Javascript для FNV-1a. Если присутствуют символы Юникода, верхняя часть отбрасывается ради производительности. Не знаю, почему было бы полезно поддерживать их при хешировании, поскольку я пока хэширую только пути 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 lower () и по-прежнему совместимых со старыми браузерами:
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 слова.
Это почти версия для одной команды / одной строки, использующая карту, сокращение ... вместо циклов, но она все еще довольно быстрая
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("");
};
мы также конвертируем вывод в hex, чтобы получить строку вместо массива слов.
Использование простое. для образца "a string".hash()
вернется"88a09e8f9cc6f8c71c4497fbb36f84cd"
Я пошел на простую конкатенацию кодов символов, преобразованных в шестнадцатеричные строки. Это служит для относительно узкой цели, а именно для того, чтобы просто было необходимо обменять хеш-представление строки SHORT (например, заголовки, теги) со стороны сервера, которая по несущественным причинам не может легко реализовать принятый порт Java hashCode. Очевидно, здесь нет приложения для обеспечения безопасности.
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"
Естественно, больше риска столкновения с этим методом, хотя вы могли бы возиться с арифметикой при уменьшении, однако вы хотели диверсифицировать и удлинить хэш.
Немного упрощенная версия ответа @ 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);
}
Я не вижу причин использовать этот слишком сложный криптографический код вместо готовых к использованию решений, таких как библиотека объектных хэшей и т. Д., Полагаясь на поставщика, более продуктивно, экономит время и снижает затраты на обслуживание.
Просто используйте 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');
, Я думаю, что он добавляет этот код зависимости от поставщика в минимизированной версии во время сборки.