Давай, я тихонько расскажу тебе кое-что о HashCode.

Java

Это первая часть того, почему технологии72оригинальная статья

В чем проблема с хеш-конфликтом

Прежде чем официально начать эту статью, несколько слов, чтобы прояснить этот вопрос:Что такое конфликт хэшей, о котором мы часто говорим?

Сразу к предыдущей картинке:

Как вы думаете, что пришло вам на ум, когда вы увидели эту картину?

Вы когда-нибудь задумывались о структуре массива плюс связанный список HashMap?

Кстати, я здесь, чтобы использовать HashMap в качестве точки входа, чтобы рассказать вам о конфликтах Hash.

Затем смотрим на следующую картинку:

Допустим сейчас у нас есть ключ значение которого [почему технология].После алгоритма Hash расчетное значение равно 1, то смысл в том, что это значение должно быть размещено в том месте, где индекс массива равен 1.

Но как показано на рисунке, на месте, где стоит индекс 1, уже висело значение eat. Эта яма уже занята.

Тогда в этот момент,Мы называем это явление хэш-конфликтом.

Как HashMap разрешает конфликты хэшей?

Метод адресной цепочки, также известный как метод застежки-молнии.

В массиве есть конфликт хэшей, и в этот момент пригодится структура данных связанного списка.

Как используется связанный список? Посмотрите на картинку:

Эта проблема решается нами.

По сути, коллизия хэшей — это то же самое:Разные объекты получают один и тот же HashCode после прохождения одного и того же алгоритма Hash.

Поэтому, когда я писал это, я внезапно подумал о вопросе для интервью:

Могу я спросить, какая версия HashMap на изображении выше основана на JDK?

Почему вы подумали об этом вопросе интервью?

Потому что я колебался около 0,3 секунды, когда рисовал картинку.Когда я повесил ее на связанный список, должен ли я использовать метод вставки головы или метод вставки хвоста?

Как мы все знаем, HashMap в JDK 7 принимает метод вставки начала, то есть [почему технология] Прежде чем [съесть], HashMap в JDK 8 использует метод вставки хвоста.

Как сказать этот вопрос интервью, действительно скучно. Но что поделаешь, восьмилапое сочинение надо выучить наизусть или мне придется его выучить.

Интервью, туда-сюда, не потёртое.

Построить строку, похожую на HashCode

Как мы знаем ранее, основной причиной конфликта хэшей является то, что разные объекты получают один и тот же хэш-код после прохождения одного и того же алгоритма хеширования.

Эта фраза звучит на первый взгляд: Ну, логично, так оно и есть, проблем нет.

Например, в нашем часто используемом HashMap ключ в большинстве случаев имеет тип String. Для возникновения конфликта Hash требуется как минимум два класса String с одинаковым HashCode.

Тогда я прошу вас:Как я могу быстро получить две строки с одним и тем же хэш-кодом?

Как ты, немного запутался?

Достаточно одного вопроса, чтобы перейти от многозначительного к легкому замешательству.

Приходите, позвольте мне показать вам волну анализа.

Позвольте мне сначала спросить вас: будут ли две разные строки длины 1, такие как следующий код, иметь одинаковый хэш-код?

String a = "a";
String b = "b";

Конечно нет, верно.

Если вы не знаете, я предлагаю вам перейти к коду ASCII, чтобы найти ответ.

Давайте посмотрим, будет ли строка длины 2 иметь тот же хэш-код?

Чтобы ответить на этот вопрос, мы должны сначала взглянуть на метод вычисления hashCode String, Здесь я беру JDK 8 в качестве примера:

Предположим, что эти две строки длины 2 равны xy и ab соответственно.

Обратите внимание, что xy и ab здесь являются заполнителями, а не строками.

Подобно неизвестным x и y в квадратном уравнении в учебниках для начальной школы, нам нужно ввести их в метод hashCode выше для вычисления.

Алгоритм hashCode, наиболее важным является цикл for.

Внутри цикла for есть три вещи, о которых мы не знаем: h, value.length и val[i]. Давайте отладим и посмотрим:

h изначально равно 0.

Базовая структура типа String — это массив символов, вы должны это знать.

Итак, value.length — это длина строки. val[] - это массив символов.

Поместите xy в цикл for, этот цикл for будет повторяться 2 раза.

Первый цикл: h=0, val[0]=x, поэтому h=31*0+x, то есть h=x.

Второй цикл: h=x, val[1]=y, поэтому h=31*x+y.

Итак, после расчета хэш-код xy равен 31 * x + y.

Точно так же хэш-код ab равен 31*a+b.

Поскольку мы хотим построить строки, подобные hashCode, мы получаем уравнение:

31x+y=31a+b

Тогда возникает вопрос: что такое x, y, a, b?

Вы можете понять это?

Вы можете считать молоток! Вы либо не хотите решать перестановки и комбинации на доске, либо просто не можете их решить.

Но я могу решить ее и показать вам, как решить эту проблему.

Начинается урок математики. Заметьте, я собираюсь деформироваться.

31x+y=31a+b можно преобразовать в:

31x-31а=б-у.

То есть 31(x-a)=b-y.

В этот момент все гораздо яснее, очевидно, что приведенное выше уравнение имеет особое решение:

х-а=1, Ь-у=31.

Потому что из вышесказанного получаем:Для любых двух строк xy и ab, если они удовлетворяют условиям xa=1, то есть значение кода ASCII первого символа отличается на 1, и в то же время удовлетворяют условиям by=31, то есть значению кода ASCII второго характер отличается на -31. Тогда хэш-код этих двух символов должен быть равен.

Уже так ясно было сказано, такое сочетание встречается против кодовой таблицы ASCII, не много ли?

Аа и ВВ, верно?

Ab и BC, не так ли?

Ac и BD, какие-нибудь?

В ПОРЯДКЕ. Теперь мы можем сгенерировать две строки с одним и тем же HashCode.

Мы немного углубляем сложность. Что делать, если я хочу создать более двух строк с одним и тем же HashCode?

Давайте сначала проанализируем его.

HashCode Aa и BB одинаковы. Если мы поместим их парами, разве это не будет то же самое?

Например: АаВВ, ВВАа.

как до меня"Шок! В ConcurrentHashMap тоже есть бесконечный цикл? 》Примеры, встречающиеся в этой статье, AaAa, BBBB:

Видите ли, произошло что-то волшебное.

Теперь у нас есть 4 строки с одинаковым хэш-кодом.

С этими 4 строками мы собираемся комбинировать с Aa, BB, например, AaBBAa, BBAaBB...

4*2=8 комбинаций, мы снова можем получить 8 строк с тем же hashCode.

Подождите, кажется, я нашел какую-то закономерность.

Если мы используем Aa и BB в качестве начальных данных, мы можем получить любое количество строк с одним и тем же хэш-кодом после нескольких перестановок и комбинаций. Длина строки увеличивается по мере увеличения числа.

Я до сих пор не знаю текста четко, просто покажу вам код напрямую, как показано ниже:

public class CreateHashCodeSomeUtil {

    /**
     * 种子数据:两个长度为 2 的 hashCode 一样的字符串
     */
    private static String[] SEED = new String[]{"Aa", "BB"};
    
    /**
     * 生成 2 的 n 次方个 HashCode 一样的字符串的集合
     */
    public static List<String> hashCodeSomeList(int n) {
        List<String> initList = new ArrayList<String>(Arrays.asList(SEED));
        for (int i = 1; i < n; i++) {
            initList = createByList(initList);
        }
        return initList;
    }

    public static List<String> createByList(List<String> list) {
        List<String> result = new ArrayList<String>();
        for (int i = 0; i < SEED.length; ++i) {
            for (String str : list) {
                result.add(SEED[i] + str);
            }
        }
        return result;
    }
}

С помощью приведенного выше кода мы можем сгенерировать любое количество строк с одним и тем же хэш-кодом.

нравится:

Итак, перестаньте задавать такие вопросы, как:

С этими строками с одинаковым hashCode мы помещаем эти строки в HashMap, код выглядит следующим образом:

public class HashMapTest {
    public static void main(String[] args) {
        Map<String, String> hashMap = new HashMap<String, String>();
        hashMap.put("Aa", "Aa");
        hashMap.put("BB", "BB");
        hashMap.put("AaAa", "AaAa");
        hashMap.put("AaBB", "AaBB");
        hashMap.put("BBAa", "BBAa");
        hashMap.put("BBBB", "BBBB");
        hashMap.put("AaAaAa", "AaAaAa");
        hashMap.put("AaAaBB", "AaAaBB");
        hashMap.put("AaBBAa", "AaBBAa");
        hashMap.put("AaBBBB", "AaBBBB");
        hashMap.put("BBAaAa", "BBAaAa");
        hashMap.put("BBAaBB", "BBAaBB");
        hashMap.put("BBBBAa", "BBBBAa");
        hashMap.put("BBBBBB", "BBBBBB");
    }
}

Наконец, длина этого HashMap будет увеличена вдвое. После расширения длина массива равна 64:

Но заняты в нем только три позиции, это места с нижними индексами 0, 31, 32:

Рисунок выглядит следующим образом:

Понимаете, шипы не волнуют, массив длиной 64, хранящий 14 данных, занимает всего 3 позиции.

Этот коэффициент использования пространства слишком низок.

Итак, это даже взлом HashMap.Поздравляем, вы освоили технику взлома: хеш-конфликт Dos.

Если вы хотите узнать больше. Прочтите эту статью Брата Стоуна:«Я не ожидал, что хэш-конфликт будет так разыгрываться. Ваша служба завербована? 》.

Глядя на картинку выше, я не знаю, с тобой что-то не так?

Если нет, то позвольте мне дать вам еще одну подсказку:Под позицией индекса массива 32 подвешен связанный список длиной 8.

Разве это не так, вдруг понял. В JDK 8, каков порог для связанного списка с деревом?

Поэтому в текущем случае позиция, где индекс массива равен 32, должна быть не связным списком, а красно-черным деревом.

правильно?

Прямо молотком! Некоторые ученики несерьезны в классе, и они собьются с пути, если не будут осторожны.

это неправильно.Порог для преобразования связанного списка в красно-черное дерево — это когда количество узлов больше 8, а не когда оно равно 8.

Другими словами, операция создания дерева будет запущена только тогда, когда другой ключ с индексом 32 после вычисления хэша и значение которого отличается от предыдущего значения.

Если вы мне не верите, позвольте мне показать вам, что это за узел сейчас:

Не врал тебе? На приведенном выше рисунке хорошо видно, что 8-й узел по-прежнему является обычным узлом.

А если это узел дерева, то он должен выглядеть так:

Если вы мне не верите, давайте придем с другим хэш-конфликтом и покажем вам своими глазами, код не обманет людей.

Так как же может выйти еще один конфликт?

В самом простом случае напишите:

Не будет ли еще одного конфликта? Я такой гений, я не могу не аплодировать себе.

Что ж, давайте посмотрим на текущий статус узла:

Как он стал TreeNode, я тебе солгал?

Кроме того, я хотел бы сказать еще одну вещь о яме вопроса интервью HashMap.

Интервьюер спросил:Каковы условия преобразования связанного списка HashMap JDK 8 в красно-черное дерево?

Большинство друзей, выучивших восьмичастное эссе в интервью, точно смогут ответить:Когда длина связанного списка больше 8.

Этот ответ правильный?

правильно, но только наполовину.

Другое условие заключается в том, что красно-черное дерево будет преобразовано только тогда, когда длина массива больше 64.

В исходниках четко написано, что длина массива меньше 64, поэтому его можно расширить напрямую, а не преобразовывать в красно-черное дерево:

Похоже, что многие игнорируют условие "длина массива больше 64".

Чтобы запомнить восьминогий текст, мне еще предстоит запомнить его весь.

Например, следующий тестовый пример:

Все они попадают в позицию с индексом 0 массива.

При попадании девятого элемента BBBBAa он попадет в метод treeifyBin, но не вызовет операцию древовидности, будет выполнена только операция расширения.

Поскольку текущая длина — это длина по умолчанию, равная 16. Не соответствует условиям преобразования красно-черного дерева.

Итак, на снимке экрана ниже мы видим, что там, где метка ①, длина массива стала 32, а длина связанного списка стала 9, но узел по-прежнему является обычным узлом:

Как насчет этого, это интересно, я думаю, что так интереснее изучать HashMap.

Класс сущности как ключ

В приведенном выше примере мы используем тип String в качестве ключа в HashMap.

Эта сцена покрывает 95% нашей сцены разработки.

Но иногда класс сущности может быть помещен в HashMap в качестве ключа.

Внимание, снова вопрос интервью:Можно ли использовать классы сущностей в качестве объектов в HashMap?

Это должно быть возможно. Но есть ямы, будьте осторожны, чтобы не наступить.

Позвольте мне привести вам пример из новости, которую я видел некоторое время назад:

Предположим, я хочу собрать информацию о семьях учеников и сохранить их в HashMap.

Тогда мой ключ — это объект студента, а значение — объект информации о семье студента.

Они следующие:

public class HomeInfo {

    private String homeAddr;
    private String carName;
     //省略改造方法和toString方法
}

public class Student {

    private String name;
    private Integer age;
     //省略改造方法和toString方法

}

Тогда наш тестовый случай выглядит следующим образом:

public class HashMapTest {

    private static Map<Student, HomeInfo> hashMap = new HashMap<Student, HomeInfo>();

    static {
        Student student = new Student("why", 7);
        HomeInfo homeInfo = new HomeInfo("大南街", "自行车");
        hashMap.put(student, homeInfo);
    }

    public static void main(String[] args) {
        updateInfo("why", 7, "滨江路", "摩托");
        for (Map.Entry<Student, HomeInfo> entry : hashMap.entrySet()) {
            System.out.println(entry.getKey()+"-"+entry.getValue());
        }
    }

    private static void updateInfo(String name, Integer age, String homeAddr, String carName) {
        Student student = new Student(name, age);
        HomeInfo homeInfo = hashMap.get(student);
        if (homeInfo == null) {
            hashMap.put(student, new HomeInfo(homeAddr, carName));
        }
    }
}

В исходном состоянии в HashMap уже есть 7-летний ребенок по имени почему, он живет на улице Да Нан, а транспортное средство дома — велосипед.

Затем, однажды, он сказал учителю, что переехал на Биньцзян-роуд и заменил велосипед дома на мотоцикл.

Таким образом, учитель изменил информацию о семье ребенка на странице.

Наконец, вызывается метод updateInfo.

Эй, угадай что?

Я покажу вам вывод:

После обновления двое 7-летних детей назвали, почему они появились в их классе: один живет на улице Данан, а другой на улице Бинцзян.

Обновление добавлено, вы говорите волшебное или нет?

Феномен вышел, так что в зависимости от явления, чтобы найти код проблемы не дело рук?

Очевидно, проблема кроется в этом месте:

Извлеченная здесь информация о доме пуста, поэтому будут введены новые данные.

Итак, давайте посмотрим, почему здесь пусто.

Следуйте исходному коду hashMap.get(), чтобы посмотреть:

Место, помеченное ①, является ключом вычисления, который является хэш-кодом объекта ученика. И наш объект студента не переопределяет hashCode, поэтому вызывается метод hashCode по умолчанию.

Студент здесь новый:

Следовательно, хэш-код этого учащегося обязательно будет отличаться от предыдущего учащегося в HashMap.

Следовательно, если метка равна ③, соответствующая позиция индекса массива вкладок, полученного после вычисления хэша, равна нулю. Не войдет в суждение if, здесь он возвращает null.

Тогда решение готово появиться:Просто переопределите метод hashCode объекта.

это?

Подожди, когда вернешься, не убегай с половиной. Я еще не закончил говорить.

Затем посмотрите исходный код:

Когда выполняется метод put HashMap, метод equals используется для определения того, совпадает ли текущий ключ с ключом, существующим в таблице.

Здесь мы не переопределили метод equals, поэтому он возвращает false.

Итак, если наши методы hashCode и equals не переопределены, появится следующая диаграмма:

Если мы перепишем hashCode, не переписывая метод equals, то появится следующая диаграмма:

Одним словом:В HashMap, если вы используете объект в качестве ключа, вы должны переопределить метод hashCode объекта и метод equals. В противном случае он не только не даст ожидаемого эффекта, но и может привести к переполнению памяти.

Например, в приведенном выше примере мы помещаем его в цикл, добавляем -Xmx10m к параметру запуска, и результат работы выглядит следующим образом:

Поскольку каждый раз, когда это новый объект студента, hashCode отличается, поэтому он будет продолжать запускать операцию расширения, и, наконец, в методе изменения размера будет выдано исключение OOM.

Странные знания снова добавлены

Пока писал эту статью, пролистал книгу "Programming Ideas in Java (4th Edition)".

Strange Lore добавляет еще два.

Первый есть в этой книге, пример размещения объектов в HashMap такой:

Сурок: сурок, сурок.

Прогноз: прогноз, прогноз, прогноз.

Рассмотрим систему прогноза погоды, связывающую сурков с прогнозами.

Что за бессмертное требование эта ТМ, которую нельзя прочитать?

К счастью, брат почему-то очень хорошо осведомлен, закрыл глаза и обыскал мой склад знаний.

Вот что случилось.

В Пенсильвании, США, 2 февраля отмечается День сурка.

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

Это перекликается с тем, что судя по тому, может ли сурок увидеть тень, когда она выходит из норы, чтобы решить, закончилась ли зима.

Таким образом, говорят, что спрос имеет смысл.

Второе странное знание заключается в следующем.

Что касается метода HashCode, «Идеи программирования на Java (4-е издание)» написано следующим образом:

Я сразу заметил, что что-то не так: результат=37*результат+c.

Как мы уже говорили ранее, базовое число должно быть 31, верно?

Автор сказал, что эта формула взята из книги "Эффективная Java (1-е издание)".

Обе эти книги являются Java-библиями.Я предлагаю вам поместить Dream Link в область сообщений.

«Эффективная Java (1-е издание)» настолько устарело, что у меня есть только физические книги для 2-го и 3-го изданий.

Поэтому я искал в Интернете первое издание электронной книги и, наконец, нашел соответствующее описание:

Видно, что формула, приведенная в книге, действительно рассчитана на основе 37.

Перелистал третье издание, там же, формула дана такая:

Более того, вы заходите в интернет искать: метод вычисления hashCode of String.

Все спорят, почему 31. Число 37 упоминается редко.

На самом деле, я предположил, что метод hashCode String должен был использовать 37 в более ранних версиях JDK, а позже был изменен на 31.

Я хотел загрузить самую раннюю версию JDK, чтобы проверить ее, но искал в Интернете подходящую версию.

Почему в книге он был изменен с 37 на 31?

Вот как это объясняет автор, 1-е издание вверху, 2-е издание внизу:

Коробочная часть пытается выразить то же самое, только объект меняется с 37 на 31.

А почему он изменился с 37 на 31, автор объяснил во втором издании, то есть той части, которую я отметил подчеркиванием.

31 Есть хороший устав, который заменяет умножение сдвигами и вычитаниями для лучшей производительности:

31*i==(i

Простое изменение числа с 37 на 31 может повысить производительность.

Загадка очень интересная, если интересно, можете проверить соответствующую информацию.

Какой удивительный компьютерный мир.

Хорошо, это все для этой статьи.

Если у вас мало знаний, неизбежно будут ошибки.Если вы найдете что-то не так, вы можете указать это в области сообщений, и я это исправлю.

Спасибо за прочтение, настаиваю на оригинальности, очень приветствую и благодарю за внимание.

Я брат почему, литературный творец, который был задержан кодом. Я не большой парень, но я люблю делиться. Я теплый и интересный сычуаньский человек.

Добро пожаловать, чтобы следовать за мной.