Страницы

Поиск по вопросам

Показаны сообщения с ярлыком hashcode. Показать все сообщения
Показаны сообщения с ярлыком hashcode. Показать все сообщения

среда, 26 февраля 2020 г.

Почему одинаковый хэш-код может быть у разных объектов?

#java #hashcode


Хотел спросить: 

И так же, как для equals(), для метода hashCode() есть официальные требования, прописанные
в документации Oracle:



  Если два объекта равны
  (т.е. метод equals() возвращает true), у них должен быть одинаковый
  хэш-код.



Иначе наши методы будут лишены смысла. Проверка по hashCode(), как мы и сказали,
должна идти первой для повышения быстродействия. Если хэш-коды будут разными, проверка
вернет false, хотя объекты на самом деле равны (согласно нашему определению в методе
equals()).



  Если метод hashCode() вызывается несколько раз на одном и том же
  объекте, каждый раз он должен возвращать одно и то же число.


  Правило 1 не работает в обратную сторону. Одинаковый хэш-код может
  быть у двух разных объектов.



Я запутался помогите разобраться: 
Почему в 3 правило написано мол правило 1 не работает в обратную сторону?
 Получается 1 правило 2 объекта равны и у них хэш-код одинаковый. 
А 3 правило так же одинаковый хэш-код у 2 объектов или я не очень понимаю? Почему
в 1 правило написано если 2 объекта равны, а в 3 правило написано одинаковый хэш-код
может быть у двух РАЗНЫХ объектов, как понять разных?
    


Ответы

Ответ 1



Примечание: предполагается, что в классе переопределен метод hashCode(). По хорошему, у разных объектов хешкод должен быть разный. Но на практике иногда происходит по другому. Очень часто это происходит из-за несовершенства формулы для вычисления хешкода. Пример: хеш строки считается по длине строки: length*3. Тогда у строк foo и bar одинаковые хеши. Вообще, хешкод используется для того, чтобы можно было точно сказать, что объекты разные. Но не для того, чтобы сказать, что они одинаковые. Одинаковый хешкод - не гарантия одинаковых объектов. Обычно он используется для сравнения объектов: Допустим, у вас есть объект, в котором есть много-много полей. В большинстве случаев объекты для сравнения будут неравны. Чтобы не сравнивать кучу переменных(если объекты не равны), можно сначала сравнивать хешкод(т.к., если хеш различается, то объекты точно различны). Если хеши отличаются - можно дальше не сравнивать переменные. Если одинаковы - дальше нужно сравнить переменные(т.к., если хеши одинаковы, то это не значит, что объекты одинаковы). Ну и последнее - почти всегда возвращаемый тип метода hashCode() - int. У int есть определенный предел(от -21... до +21..., если я не ошибаюсь). Если разных объектов будет больше, чем этот предел, то физически нельзя сгенерировать разные хеши для всех объектов. Т.е., при использовании хешей можно увеличить производительность программы. Попробую объяснить правила: У одинаковых объектов всегда одинаковые хеши У одного и того же объекта всегда должен быть неизменяемый хешкод(если значения внутри объекта не изменились) У разных объектов иногда могут быть одинаковые хеши

четверг, 23 января 2020 г.

Емкость (capacity) и заполненность (load factor) для HashMap

#коллекции #hashcode


Господа,
поймал себя на том, что не понимаю базовых вещей по Hash-коллекциям.

Предположим, переопределили hashCode() таким образом, что он равен для всех экземпляров
класса, который используется в качестве ключа HashMap (или значения HashSet).
Значит ли это, что фактически все заносимые в коллекцию Entry окажутся в одной-единственной
"корзине",
или все же будет создано некоторое количество корзин (по умолчанию, кажется, 16),
в каждой из которых элементы ключи имеют один и тот же код?

Известно также, что при достижении load factor (0.75) происходит динамическое перераспеределение
корзин. Но тогда вновь вопрос - а в чем же здесь роль специфической реализации hashCode(),
если все подстраивается по каким-то внутренним алгоритмам hash-коллекций?
    


Ответы

Ответ 1



Я бы очень рекомендовал книгу Effective Java, автор Joshua Bloch (есть и на русском языке). Там описано очень много нюансов работы с Java, которые могут сильно повлиять на ваш код. По первому вопросу: Вы правы, вот выдержка из выше приведенной книги (Item 9: Always override hashCode when you override equals) // The worst possible legal hash function - never use! @Override public int hashCode() { return 42; } It’s legal because it ensures that equal objects have the same hash code. It’s atrocious because it ensures that every object has the same hash code. Therefore, every object hashes to the same bucket, and hash tables degenerate to linked lists. Таким образов, все элементы оказываются в одной корзине и все элементы будут сравниваться через equals. Про второй вопрос не очень понятно, но постараюсь ответить. Грубо говоря для определения места в HashMap мы будем использовать операцию деления по модулю (т.е. hashCode() & n, где n - количество корзин являющееся степенью двойки). Таким образом при увеличении количества корзин произойдет перераспределение (пересчет места в таблице) для нового размера. Но если, как в первом вопросе, функция hashCode() будет возвращать одинаковые значения, но никакое перераспределение не поможет, элементы останутся в одной корзине. В том и смысл - если хэш функция генерирует более равномерные результаты, корзины будут заполняться равномерно, не будет большого количества пустых корзин и перераспределение будет более эффективным. Советую так же прочитать: Структуры данных в картинках. HashMap Джошуа Блох, Java. Эффективное программирование Joshua Bloch, Effective Java

воскресенье, 12 января 2020 г.

Хешкод, переопределение метода GetHashCode

#c_sharp #c_sharp_faq #hashcode


Господа, не могу понять каким образом переопределять метод GetHashCode(). Ведь, насколько
я понял, хешкод берется из скрытой переменной в объекте, к которой нет доступа. Тогда
как мне его переопределить ?? Если не затруднит, то хотелось бы увидеть какой-то элементарный
пример.
И еще не пойму, почему разные хешкоды в коде
using System;
class a
{
    public int x;
    public a(int y)
    {
        x = y;
    }
}
class b
{
    static void Main()
    {
        Console.WriteLine(new a(5).GetHashCode() + " " + new a(5).GetHashCode());
    }
}

Ведь тут написано https://msdn.microsoft.com/ru-ru/library/system.object.gethashcode(v=vs.110).aspx


Для двух одинаковых объектов
возвращенные хэш-коды равны
    


Ответы

Ответ 1



Ведь, насколько я понял, хешкод берется из скрытой переменной в объекте, к которой нет доступа. Так ведь и метод вы переопределяете в своем же классе :). Что-то типа: class a { public int x; public a(int y) { x = y; } public override int GetHashCode() { return x; } } Есть несколько правил для переопределения GetHashCode(), основные: 1) Используемая функция должна давать хорошее распределение. Это, строго говоря, зависит от данных, однако часто хорошо подходит подобная функция: public override int GetHashCode() { int hashcode = field1.GetHashCode(); hashcode = 31 * hashcode + field2.GetHashCode(); hashcode = 31 * hashcode + field3.GetHashCode(); // и т.д. для остальный полей return hashcode; } 2) Эта функция должна быть быстрой. 3) GetHashCode() не должен выбрасывать исключения. 4) В идеале GetHashCode() не должен меняться в течение жизни объекта, т.е. полагаться только на неизменяемые члены класса. На практике этим часто пренебрегают, пока не стрельнет. Так же не забудьте, что Equals() и GetHashCode() всегда должны идти в паре: переопределили один метод, переопределяйте и другой. И если два объекта равны, то у них должен быть одинаковый хэшкод. Обратное необязательно верно (хэш-функция может вернуть одинаковое дначение для разных объектов). Что почитать (на английском): Про правильное переопределение GetHashCode() Про хэш-функции

понедельник, 6 января 2020 г.

в Java hashTable.hashCode() всегда возвращает 0

#java #коллекции #структуры_данных #hashcode


Есть структура(класс).

    public class Node {
        private static String separator = "\\";
        private Map fileMap = new HashMap<>();
        private Node parent;
        private String absolutePath;
        private Integer hash;
        public void addFile(File file) {

        fileMap.put(file.getAbsolutePath(), new Node(file.getAbsolutePath(), this));
            hash = hashCode();
        }

        public Node(String absolutePath, Node parent) {
            this.absolutePath = absolutePath;
            this.parent = parent;
        }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Node)) return false;

        Node node = (Node) o;

        if (getFileMap() != null ? !getFileMap().equals(node.getFileMap()) : node.getFileMap()
!= null) return false;
        return getAbsolutePath().equals(node.getAbsolutePath());
    }

    @Override
    public int hashCode() {
        boolean eq = getFileMap() != null;
        int result;
        if (eq){
            result = getFileMap().hashCode();
            result += 0;
        } else
            result = 0;
        result = 31 * result + getAbsolutePath().hashCode();
        return result;
    }


Данная структура используется для представление иерархии файловой системы. То есть
node может содержать сам файл, так и hashTable (fileMap) с вложенными файлами, если
это директория, также файлы в hashTable (fileMap) могут быть файлами или директориями.

Специально переопределил метод hashCode() через if для наглядности.
Строка result = getFileMap().hashCode(); всегда возвращает 0, сколько бы разных сущностей
в ней не было.
В итоге hash считается только от параметра "absolutePath". Что меня не устраивает,
ведь мне нужно, чтобы хэш был разным в зависимости от вложенных файлов. 
Иначе получается ситуация в которой, два дерева представляющих файловую систему,
например:

testdir/innerdir/1/2

testdir/outdir

и содержащие одинаковую папку в root - "testdir" равны, т.к. хэш вложенных структур
в hashTable не учитывается.
Может кто-нибудь сказать Почему так происходит?
    


Ответы

Ответ 1



Если посмотреть в исходный код HashMap (точнее его родителя AbstractMap),то можно увидеть как реализовано метод hashCode().Он суммирует хеш коды всех элементов: public int hashCode(){ int h=0; Iterator>i=entrySet().iterator(); while(i.hasNext()) h+=i.next().hashCode(); return h; } Далее смотрим как реализуют этот метод элементы HashMap - HashMapEntry: public final int hashCode() { return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); } Здесь используется исключающее ИЛИ для хешкодов ключа и значения. То есть, если хешкоды ключа и значение будут равны, то метод вернёт 0. В итоге получаем, что когда getFileMap() == null, то для этого Node хешкод берётся от getAbsolutePath(). Этот же getAbsolutePath() является ключём в мапе, получается, что хешкоды ключа и значения равны, отсюда и возвращается 0.

пятница, 20 декабря 2019 г.

Расчет коллизий при CRC32

#алгоритм #хеширование #hashcode #crc


Возник вопрос - если существует значение функции CRC32, например - a50985e0 которое
было получено из массива байт - Hello (т.е. из строки ТОЛЬКО символов.) то какова вероятность
что точно такое же значение (a50985e0) появится при обработке функцией массива байт
полученных из 12345 т.е. из числа ?

Upd.

Исходя из ответов уважаемых @Alex и @Harry делаю вывод что коллизий не избежать в
принципе. И при постаточно большем диапазоне числового массива (от 0 до 1 000 000 000)
всегда найдется хеш, который совпадет с хешем полученным из строки символов. В связи
с этим переформулирую вопрос - существует ли закономерность (возможно ли ее вообще
обнаружить) в колличестве этих самых коллизий ? Например хеш X из числа Y (например
12345) проверен в обшем массиве хешей (0 - 2 000 000 000) и найдено 10 коллизий, и
так же для любого числа - врезультируещем массиве существует хотябы 10 коллизий. В
то время как хеш из символьной строки Hello даст всего 1 коллизию и так же любая другая
строка из символов даст не более 1 коллизии. Существует ли подобная законормерность ?
    


Ответы

Ответ 1



Вначале хочу заметить, если вы задаетесь таким вопросом, то скорее всего вы используйте функцию чек-суммы не по прямому назначению. А значит, вы делаете что-то неправильно. Насколько я понял, вам интересно, одинаков ли шанс получить коллизию, если на входе строка из букв, или строка из цифр. Хочу заметить, что если у вас откуда-то приходят случайные строки длиной 5 символов (для примера), то шанс что у вас появятся одинаковые строки из цифр на много-много порядков выше, чем когда приходят строки из букв. Что бы показать, что шансы получить коллизию одинаковы, я сгенерировал 100 млн чек-сумм для строк длиной 20 (я взял 20, что бы не создавались одинаковые строки из чисел), и посчитал коллизии внутри строк из чисел, внутри строк из букв, и взаимные коллизии между строками из чисел и букв. Вот мой код на C#: private static uint crc32(byte[] data) { uint crc = 0xffffffff; uint poly = 0xedb88320; for (int i = 0; i < data.Length; i++) { uint c = (crc ^ data[i]) & 0xff; for (int k = 0; k < 8; k++) c = (c & 1) != 0 ? poly ^ (c >> 1) : c >> 1; crc = c ^ (crc >> 8); } return crc ^ 0xffffffff; } static void Main(string[] args) { byte[][] digits = new byte[0x10000][]; for (int i = 0; i < 0x10000; i++) digits[i] = new byte[0x10000]; byte[][] chars = new byte[0x10000][]; for (int i = 0; i < 0x10000; i++) chars[i] = new byte[0x10000]; var rnd = new Random(123); int iterations = 100000000; byte[] buffer = Encoding.ASCII.GetBytes("12345678901234567890"); for (int i = 0; i < iterations; i++) { int index = rnd.Next(20); buffer[index]++; if (buffer[index] > '9') buffer[index] = (byte)'0'; uint crc = crc32(buffer); digits[crc >> 16][crc & 0xFFFF]++; } buffer = Encoding.ASCII.GetBytes("abcdefwxyzabcdefwxyz"); for (int i = 0; i < iterations; i++) { int index = rnd.Next(20); buffer[index]++; if (buffer[index] > 'z') buffer[index] = (byte)'a'; uint crc = crc32(buffer); chars[crc >> 16][crc & 0xFFFF]++; } int digitsCollisions = 0; for (int i = 0; i < 0x10000; i++) for (int j = 0; j < 0x10000; j++) if (digits[i][j] > 1) digitsCollisions += digits[i][j]; int charsCollisions = 0; for (int i = 0; i < 0x10000; i++) for (int j = 0; j < 0x10000; j++) if (chars[i][j] > 1) charsCollisions += chars[i][j]; int digitsCharsCollisions = 0; for (int i = 0; i < 0x10000; i++) for (int j = 0; j < 0x10000; j++) if (chars[i][j] > 0 && digits[i][j] > 0) digitsCharsCollisions += chars[i][j] * digits[i][j]; Console.WriteLine(digitsCollisions); Console.WriteLine(charsCollisions); Console.WriteLine(digitsCharsCollisions); } Результат: 2298490 2304243 2330855 Как видно, вероятность получить коллизию одинаковая. В другом тесте я брал длину входных данных 10 байт, и вместо случайных чисел просто брал строковое представление числа i. Результат получился другой: 4431872 2301156 2324554 Как видно, коллизий на числах в 2 раза больше, хотя чек-сумма считалась на уникальных входных данных. Это происходит потому, что в случайной строке из букв длиной 10 байт больше энтропии, чем в строке чисел. CRC-32 не является полноценной криптографической хеш-фукнцией, и в ней нет лавинного эффекта. С хорошей хеш-функцией мы бы получили одинаковый результат.

Ответ 2



Если хэш-функция "хорошая", то понять от чего она возникла (цифры, или буквы) нельзя. Но! CRC32 дает повтор с вероятностью более 50% (сюрприз!) уже на 80 тысячах входных строк.

Ответ 3



Сообразил. В вашей постановке задачи - вероятность 1. Всегда найдется такая последовательность не из цифр, которая даст вам то же значение crc32. Если постановка задачи - с какой вероятностью существует строка не из цифр, совпадающая по crc32 с одним из данных значений.

Ответ 4



Мне кажется ответ на ваш вопрос в этом утверждении Для любого CRC32 хеша всегда существует единственная последовательность из четырех байт, хеш которых даст заданный (данное утверждение следует из того, что все 256 значений полинома имеют различный старший байт) Исходя из этого утверждения, коллизий у вас будет столько, сколько четверок байт в сообщении максимальной длины

среда, 18 декабря 2019 г.

Зависимость работы HashSet от переопределения equals() и hashCode() у элементов множества

#java #hashcode #hashset #equals


Мне дали задание сделать два разных объекта класса User с одинаковыми полями и 4
варианта их добавления в HashSet:


Не переопределять ни equals(), ни hashCode() у класса User.
Переопределить только equals().
Переопределить только hashCode().
Переопределить и equals(), и hashCode().


Во всех случаях нужно сказать какой будет size и почему.

Класс User выглядит так:

public class User {
    public String name;
    public int children;
    public Calendar birthday;

    public User(String name, int children, Calendar birthday) {
        this.name = name;
        this.children = children;
        this.birthday = birthday;
    }
}


hashCode() я переопределяю так:

@Override
public int hashCode() {
    int hash = 31;
    hash = hash * 17 + name.hashCode();
    hash = hash * 17 + birthday.hashCode();
    hash = hash * 17 + children;
    return hash;
}


equals() я переопределяю так:

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (this.getClass() != obj.getClass())
        return false;
    UserEquals ue = (UserEquals) obj;
    return this.hashCode() == ue.hashCode() ||
            (this.birthday.getTimeInMillis() == ue.getBirthday().getTimeInMillis() &&
            this.children == ue.getChildren() &&
            this.name.equals(ue.getName()));
}


Если мои варианты переопределения методов некорректны, прошу меня поправить.

Тест в общем случае выглядит так:

@Test
public void whenThen() {
    Calendar calendar = new GregorianCalendar(1988, Calendar.JUNE, 19);
    User user1 = new User("Pavel", 0, calendar);
    User user2 = new User("Pavel", 0, calendar);
    Set set = new HashSet<>();
    set.add(user1);
    set.add(user2);
    int result = set.size();
    assertThat(result, is(2));
}


И вот вопрос:

Когда я не переопределяю ничего у класса User, то у меня size == 2, и когда я переопределяю
только equals() - тоже size == 2. И я подумал, что HashSet вообще не использует метод
equals(), а сразу использует hashCode() (то есть сравнение по содержанию полей в equals()
на ситуацию не влияет). 

Но когда я переопределил отдельно только hashCode(), ситуация также не изменилась:
size по-прежнему остался равен 2. И только когда я переопределил оба метода (и hashCode(),
и equals()), size стал равен 1.

Сколько ни блуждаю по исходникам HashSet, не могу до конца понять как же оно всё-таки
работает.

В чем идея класса HashSet при выявлении что считать дубликатом, а что - нет?
    


Ответы

Ответ 1



Чтобы понять почему получились такие результаты, нужно понять, как работает хэш таблица. Для методов equals и hashCode установлен определенный контракт: если элементы равны между собой, т.е. equals возвращает true, то значение hashCode для этих объектов должно совпадать. если значение hashCode для объектов совпадает, то это еще не значит, что equals для них вернет true, т.е. объекты не обязаны быть равны между собой, т.е. возможны коллизии. И так, рассмотрим хэш таблицу с bucket'ами. Она состоит из массива, в каждой ячейке которого хранится список элементов. Когда мы добавляем элемент, для него вычисляется кэшкод, затем по определяется индекс ячейки, где должен хранится список, содержащий данный элемент: index = hashcode % table_size где table_size - это размер таблицы. По индексу достается список, методом equals проверяется, содержится ли данный элемент в списке, если нет, то он добавляется. Аналогично, выполняется операции remove, contains. Теперь рассмотрим каждый случай по отдельности: equals и hashCode не переопределены, это значит, что equals будет возвращать true только в случае если ссылки равны, а hashCode может быть как равен так и нет. Размер, не зависимо от значения hashCode, будет 2. equals и hashCode переопределены, тогда у нас будет одинаковое хэш значение, мы попадем в одну и туже ячейку таблицы, equals определит, что объект в списке уже присутствует, соответственно размер будет равен 1. equals непереопределен, а hashCode переопределен. В этом случае, индекс ячейки будет одним и тем же, но в списке не обнаружится одинакового элемента и по этому размер будет равен 2. equals переопределен, а hashCode непереопределен. Здесь зависит того как генерируется значение для hashCode в классе Object. Если значения будут одинаковы, то список будет один и тот же, и соответсвенно, количество элементов в таблице будет 1. Если разные, то поиск будет происходить в разных списках, и дубликатов не обнаружится, тогда размер будет равен 2.

Ответ 2



(обязательно прочитайте последний пункт, связанный с изменениями в JDK 8) Как работает HashSet Рассмотрим исходный код метода boolean add(E e) класса HashSet: public boolean add(E e) { return map.put(e, PRESENT)==null; } Отсюда видно, что при добавлении элемента в HashSet происходит добавление этого элемента в map, который является объектом класса HashMap: private transient HashMap map; В качестве ключа здесь используется переданный в метод add(...) объект, а в качестве значения – объект класса Object: // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); Отсюда делаем вывод, что HashSet работает ровно так же, как и HashMap с поправкой на то, что в качестве значения используется некоторая заглушка – один и тот же объект класса Object. Как работает HashMap HashMap работает по принципам хэширования, за счет которого доступ к элементам по ключу достигается в лучшем случае за константное время – O(1). HashMap хранит элементы вида ключ-значение в массиве: transient Node[] table; где Node – вложенный класс со следующей структурой: static class Node implements Map.Entry { final int hash; final K key; V value; Node next; ... } Как видно, в Node хранится хэш, ключ, значение и ссылка не следующий элемент (об этом поле расскажу далее). В теории хэширования существует такое понятие как коллизия – это явление, когда для разных объектов получается одинаковый хэш-код. В Java хэш-код имеет тип int, следовательно, множество хэш-кодов ограничено множеством значений типа int – [-2147483648;2147483647]. Множество же объектов ограничено только Вашей фантазией, следовательно, возможна ситуация, когда различные объекты будут иметь один и тот же хэш-код. Вкратце, процесс добавления пары key-value в HashMap выглядит следующим образом: Если key == null, то вызывается метод putForNullKey(...) в который передается value. Этот метод добавляет key-value в нулевую позицию массива; Если key != null, то вычисляется хэш-код объекта key, который передается в метод hash(...): static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } По полученному из метода hash(...) хэш-коду вычисляется индекс массива, по которому необходимо разместить данную пару key-value: static int indexFor(int h, int length) { return h & (length-1); } Если по полученному индексу в массиве не содержится ничего, то данная пара размещается по этому индексу. Если по полученному индексу уже находится какая-либо пара (либо пары), то происходит последовательный обход всех пар с проверкой равенства хэш-кода и ключа добавляемой пары с соответствующими значениями уже находящихся в данной корзине пар. Для сравнения хэш-кода используется ==, а для сравнения ключа == или key.equals(...). При совпадении этих параметров, значение элемента перезаписывается. Таким образом, каждый элемент массива (корзина, bucket) представляет собой связный список (linked list), в котором последовательно хранятся элементы. Отсюда становится понятно, что: В лучшем случае (когда в одной корзине находится не более одного элемента) – значение по ключу можно получить за O(1); В худшем случае (когда все элементы находятся в одной корзине, а искомый элемент – последний в списке) – значение по ключу можно получить за O(n) (так как придется последовательно обойти все элементы списка). Одно очень важное замечание В JDK 7 и ниже, для хранения пар в одной корзине используется linked list; В JDK 8 для этой цели используется balanced tree, следовательно, в худшем случае, значение по ключу может быть получено уже за O(log n). Возможно, в приведенном выше тексте есть некоторые неточности, но в целом алгоритм работы HashMap именно такой. Ответ на Вашу непосредственную задачу расписывать не буду, так как согласен с ответом коллеги.

Как будет hash (хеш) по-русски?

#хеширование #hashcode


Для написания дипломных работ требуют использования только русских слов, а не заимствованных
из английского языка. Например нельзя использовать: брандмауэр, логин, браузер.  На
английском можно только название программ, ОС и т.д. Друзья, есть ли у Вас идеи, как
называют хеш (в моем случае хеш пароля) в научной литературе на русском?
Я встречал - "дайджест сообщения", но "дайджест" тоже английское слово "digest".
Видел перевод "сводка сообщения" в википедии, но никаких материалов, где такое словосочетание
используется нет.    


Ответы

Ответ 1



В ГОСТ Р 34.11-94, начиная прямо с названия, используется слово «хэш» и его производные («хэширование»). Так что с этим словом все в порядке. За исключением вопроса правописания («хэш» или «хеш»). В ГОСТах тут слегка разброд и шатания, два ГОСТа одного года (34.10-94 и 34.11-94; да, первый неактуален, см. далее) использовали разные варианты («хеш» и «хэш» соотв.), в ГОСТ 34.10-2001 уже «хэш.» Так что, скорее всего, через «э», но тут я не уверен. Это, лучше, спросите на gramota.ru :)

Ответ 2



Формально может быть подходит контрольная сумма, но лично у меня возникают не те ассоциации. В советской научной литературе (насколько помню) для организации поиска в хэш-таблицах когда-то применяли термин перемешивание (и производные от него), но он был успешно вытеснен хэшированием. Вместо "дайджест" можно было бы использовать подпись, но это несколько конфликтует с смыслом, вкладываемом ныне в термин ЭЦП (электронная цифровая подпись). А по делу, могу только выразить Вам свои самые искренние соболезнования.

Ответ 3



И хеш, и дайджест есть в википедии, как и само понятия "хеширование", которое я многократно встречал в русскоязычной технической литературе. Так что, я думаю, можете смело использовать! Другое дело с логином, браузером и брендмауэром - для них википедия предлагает "идентификатор учетной записи", "веб-обозреватель" и "межсетевой экран". А слово "хеш" - официально используется в русском языке, и при этом, конечно, является заимствованным. Я думаю вы немного ошибаетесь, когда пишете, что нельзя использовать именно заимствованные слова, ведь заимствованными по факту являются почти все названия наук и большая часть терминов. Я уверен, что вам запретили использовать именно слова взятые из английского языка и записанные русскими буквами с максимально похожим произношением, при этом не входящие официально в состав русского языка.

суббота, 30 ноября 2019 г.

Использование чисел при переопределении метода hashCode

#java #hashcode


Да, в гугле можно найти статью 
Разбираемся с hashCode() и equals(), где вполне доступным языком описаны правила
переопределения hashCode() и equals(). В крайнем случае можно взять того же Блоха с
его Философией Java и посмотреть там эти правила. Речь не об этом. Правила мне понятны.

Непонятно вот что:

@Override
public int hashCode() {
    int hash = 37;
    hash = hash * 17 + str1.hashCode();
    hash = hash * 17 + str2.hashCode();
    hash = hash * 17 + num;                      
    return hash;
}


В разных примерах за основу берут какие-то числа. В данном примере взяты 37 и 17.
Таких примеров в Интернете масса, но в каждом из них числа разные. В одном примере
вообще встретилась такая конструкция:

@Override
public int hashCode() {
    int hash = new Random().nextInt(255);
    hash = hash * 255 + dozer.hashCode();
    hash = hash * 255 + tank.hashCode();              
    return hash;
}


Что окончательно сбило меня с толку. Как всё же правильно переопределять хеш-код? 

В связи с этим ряд вопросов:


Нужно ли вообще использовать какие-то числа?
Есть ли разница какое число выбирать? Или это некая "договорённость" внутри команды
при разработке продукта? 
Существуют ли ограничения на выбор стартового числа?
Почему каждый раз хеш нужно умножать сам на себя (прибавление хешей полей объекта
мне понятно)?

    


Ответы

Ответ 1



Почему используется 37 и 17 ? Как правило, в качестве начального значения выбирается простое число, это сделано для уменьшения вероятности возникновения коллизии. Смысл какое число выбирать, конечно, имеет. Если вы будете хранить ваши объекты в хэш таблице, то ее производительность прямо зависит от реализации hashCode() см. пункт 1 Для того, чтобы значения хэша были максимально отличны для объектов имеющих одинаковые значения полей. Этим мы добиваемся равномерного распредления ключей, в хэш таблице.

Ответ 2



Вот в этой статье упоминаются такие числа - Заметки о реализации hashCode() в Java. Совсем не обязательно использовать какие-то "магические" числа, скорее всего в примерах это сделано для уменьшения коллизий. Например, hashCode класса Integer просто возвращает число, которое было записано в переменную, т.е. внутри никакого умножения на какое-либо число нет.

Почему при переопределении hashCode чаще всего используют просто число 31?

#java #hashcode


Почему при переопределении hashCode чаще всего используют просто число 31?
Где-то слышал, что это число, было выбрано, с математической точки зрения, так как
оно обеспечивает равномерное распределение hashCode функции, что обеспечивает минимальную
вероятность появления коллизий.
    


Ответы

Ответ 1



В Effective Java Джошуа Блоха говорится, что: 31 было выбрано так как это нечётное простое число. Если вопрос в том, почему именно 31, то это потому что операция умножения может быть заменена сдвигом и вычитанием для повышения производительности: 31 * i == (i << 5) - i. Современные виртуальные машины делают такого рода оптимизации автоматически. Можно так же почитать это: Из оставшихся четырех я, наверное, выберу P(31), так как его дешевле вычислять на RISC архитектурах (потому что 31 разность двух степеней двойки). Р(33) так же дешева для вычисления, но его производительность незначительно хуже, и 33 - составное, что заставляет меня нервничать.

суббота, 9 марта 2019 г.

Хешкод, переопределение метода GetHashCode

Господа, не могу понять каким образом переопределять метод GetHashCode(). Ведь, насколько я понял, хешкод берется из скрытой переменной в объекте, к которой нет доступа. Тогда как мне его переопределить ?? Если не затруднит, то хотелось бы увидеть какой-то элементарный пример. И еще не пойму, почему разные хешкоды в коде using System; class a { public int x; public a(int y) { x = y; } } class b { static void Main() { Console.WriteLine(new a(5).GetHashCode() + " " + new a(5).GetHashCode()); } } Ведь тут написано https://msdn.microsoft.com/ru-ru/library/system.object.gethashcode(v=vs.110).aspx Для двух одинаковых объектов возвращенные хэш-коды равны


Ответ

Ведь, насколько я понял, хешкод берется из скрытой переменной в объекте, к которой нет доступа. Так ведь и метод вы переопределяете в своем же классе :). Что-то типа: class a { public int x;
public a(int y) { x = y; }
public override int GetHashCode() { return x; } } Есть несколько правил для переопределения GetHashCode(), основные: 1) Используемая функция должна давать хорошее распределение. Это, строго говоря, зависит от данных, однако часто хорошо подходит подобная функция: public override int GetHashCode() { int hashcode = field1.GetHashCode(); hashcode = 31 * hashcode + field2.GetHashCode(); hashcode = 31 * hashcode + field3.GetHashCode(); // и т.д. для остальный полей return hashcode; } 2) Эта функция должна быть быстрой. 3) GetHashCode() не должен выбрасывать исключения. 4) В идеале GetHashCode() не должен меняться в течение жизни объекта, т.е. полагаться только на неизменяемые члены класса. На практике этим часто пренебрегают, пока не стрельнет. Так же не забудьте, что Equals() и GetHashCode() всегда должны идти в паре: переопределили один метод, переопределяйте и другой. И если два объекта равны, то у них должен быть одинаковый хэшкод. Обратное необязательно верно (хэш-функция может вернуть одинаковое дначение для разных объектов). Что почитать (на английском): Про правильное переопределение GetHashCode() Про хэш-функции

пятница, 14 декабря 2018 г.

Расчет коллизий при CRC32

Возник вопрос - если существует значение функции CRC32, например - a50985e0 которое было получено из массива байт - Hello (т.е. из строки ТОЛЬКО символов.) то какова вероятность что точно такое же значение (a50985e0) появится при обработке функцией массива байт полученных из 12345 т.е. из числа ?
Upd.
Исходя из ответов уважаемых @Alex и @Harry делаю вывод что коллизий не избежать в принципе. И при постаточно большем диапазоне числового массива (от 0 до 1 000 000 000) всегда найдется хеш, который совпадет с хешем полученным из строки символов. В связи с этим переформулирую вопрос - существует ли закономерность (возможно ли ее вообще обнаружить) в колличестве этих самых коллизий ? Например хеш X из числа Y (например 12345) проверен в обшем массиве хешей (0 - 2 000 000 000) и найдено 10 коллизий, и так же для любого числа - врезультируещем массиве существует хотябы 10 коллизий. В то время как хеш из символьной строки Hello даст всего 1 коллизию и так же любая другая строка из символов даст не более 1 коллизии. Существует ли подобная законормерность ?


Ответ

Вначале хочу заметить, если вы задаетесь таким вопросом, то скорее всего вы используйте функцию чек-суммы не по прямому назначению. А значит, вы делаете что-то неправильно.
Насколько я понял, вам интересно, одинаков ли шанс получить коллизию, если на входе строка из букв, или строка из цифр. Хочу заметить, что если у вас откуда-то приходят случайные строки длиной 5 символов (для примера), то шанс что у вас появятся одинаковые строки из цифр на много-много порядков выше, чем когда приходят строки из букв.
Что бы показать, что шансы получить коллизию одинаковы, я сгенерировал 100 млн чек-сумм для строк длиной 20 (я взял 20, что бы не создавались одинаковые строки из чисел), и посчитал коллизии внутри строк из чисел, внутри строк из букв, и взаимные коллизии между строками из чисел и букв. Вот мой код на C#:
private static uint crc32(byte[] data) { uint crc = 0xffffffff; uint poly = 0xedb88320; for (int i = 0; i < data.Length; i++) { uint c = (crc ^ data[i]) & 0xff; for (int k = 0; k < 8; k++) c = (c & 1) != 0 ? poly ^ (c >> 1) : c >> 1; crc = c ^ (crc >> 8); } return crc ^ 0xffffffff; }
static void Main(string[] args) { byte[][] digits = new byte[0x10000][]; for (int i = 0; i < 0x10000; i++) digits[i] = new byte[0x10000];
byte[][] chars = new byte[0x10000][]; for (int i = 0; i < 0x10000; i++) chars[i] = new byte[0x10000];
var rnd = new Random(123); int iterations = 100000000;
byte[] buffer = Encoding.ASCII.GetBytes("12345678901234567890"); for (int i = 0; i < iterations; i++) { int index = rnd.Next(20); buffer[index]++; if (buffer[index] > '9') buffer[index] = (byte)'0';
uint crc = crc32(buffer); digits[crc >> 16][crc & 0xFFFF]++; }
buffer = Encoding.ASCII.GetBytes("abcdefwxyzabcdefwxyz"); for (int i = 0; i < iterations; i++) { int index = rnd.Next(20); buffer[index]++; if (buffer[index] > 'z') buffer[index] = (byte)'a';
uint crc = crc32(buffer); chars[crc >> 16][crc & 0xFFFF]++; }
int digitsCollisions = 0; for (int i = 0; i < 0x10000; i++) for (int j = 0; j < 0x10000; j++) if (digits[i][j] > 1) digitsCollisions += digits[i][j];
int charsCollisions = 0; for (int i = 0; i < 0x10000; i++) for (int j = 0; j < 0x10000; j++) if (chars[i][j] > 1) charsCollisions += chars[i][j];
int digitsCharsCollisions = 0; for (int i = 0; i < 0x10000; i++) for (int j = 0; j < 0x10000; j++) if (chars[i][j] > 0 && digits[i][j] > 0) digitsCharsCollisions += chars[i][j] * digits[i][j];
Console.WriteLine(digitsCollisions); Console.WriteLine(charsCollisions); Console.WriteLine(digitsCharsCollisions); }
Результат:
2298490 2304243 2330855
Как видно, вероятность получить коллизию одинаковая.
В другом тесте я брал длину входных данных 10 байт, и вместо случайных чисел просто брал строковое представление числа i. Результат получился другой:
4431872 2301156 2324554
Как видно, коллизий на числах в 2 раза больше, хотя чек-сумма считалась на уникальных входных данных. Это происходит потому, что в случайной строке из букв длиной 10 байт больше энтропии, чем в строке чисел. CRC-32 не является полноценной криптографической хеш-фукнцией, и в ней нет лавинного эффекта. С хорошей хеш-функцией мы бы получили одинаковый результат.

пятница, 7 декабря 2018 г.

Как будет hash (хеш) по-русски?

Для написания дипломных работ требуют использования только русских слов, а не заимствованных из английского языка. Например нельзя использовать: брандмауэр, логин, браузер. На английском можно только название программ, ОС и т.д. Друзья, есть ли у Вас идеи, как называют хеш (в моем случае хеш пароля) в научной литературе на русском? Я встречал - "дайджест сообщения", но "дайджест" тоже английское слово "digest". Видел перевод "сводка сообщения" в википедии, но никаких материалов, где такое словосочетание используется нет.


Ответ

В ГОСТ Р 34.11-94, начиная прямо с названия, используется слово «хэш» и его производные («хэширование»). Так что с этим словом все в порядке. За исключением вопроса правописания («хэш» или «хеш»). В ГОСТах тут слегка разброд и шатания, два ГОСТа одного года (34.10-94 и 34.11-94; да, первый неактуален, см. далее) использовали разные варианты («хеш» и «хэш» соотв.), в ГОСТ 34.10-2001 уже «хэш.» Так что, скорее всего, через «э», но тут я не уверен. Это, лучше, спросите на gramota.ru :)

пятница, 5 октября 2018 г.

Почему при переопределении hashCode чаще всего используют просто число 31?

Почему при переопределении hashCode чаще всего используют просто число 31? Где-то слышал, что это число, было выбрано, с математической точки зрения, так как оно обеспечивает равномерное распределение hashCode функции, что обеспечивает минимальную вероятность появления коллизий.


Ответ

В Effective Java Джошуа Блоха говорится, что:
31 было выбрано так как это нечётное простое число.
Если вопрос в том, почему именно 31, то это потому что операция умножения может быть заменена сдвигом и вычитанием для повышения производительности: 31 * i == (i << 5) - i. Современные виртуальные машины делают такого рода оптимизации автоматически.
Можно так же почитать это
Из оставшихся четырех я, наверное, выберу P(31), так как его дешевле вычислять на RISC архитектурах (потому что 31 разность двух степеней двойки). Р(33) так же дешева для вычисления, но его производительность незначительно хуже, и 33 - составное, что заставляет меня нервничать.