HashMap - это реализация интерфейса карты, основанную на хэш -таблицах, обеспечивая все дополнительные операции отображения, а также обеспечивает нулевые значения и нулевую конструкцию, из синхронизации и отсутствия гарантии порядка отображения. Давайте записать принцип реализации HashMap.
Hashmap Внутренняя хранилище
В HashMap все отношения пары ключевых и стоимость хранятся путем поддержания массива таблицы мгновенных переменных (также известных как ведро). Ведро представляет собой массив входных объектов. Размер ведра может быть изменен по мере необходимости, а длина должна быть мощностью 2. Следующий код:
/ *** Пустое массив записи, значение по умолчанию ведра*/ статическая конечная запись <?,?> [] Empty_table = {}; / *** ведро, изменение размера по мере необходимости, но должна быть мощностью 2*/ переходной записи <K, v> [] table = (inpit <k, v> []) empty_table;Начальная емкость и коэффициент нагрузки
HashMap имеет два параметра, которые влияют на производительность, начальную емкость и коэффициент нагрузки. Емкость - это количество ведер в таблице хэш, начальная емкость - это только емкость хэш -таблицы при создании, а коэффициент нагрузки - это шкала, в которой хэш -таблица может достичь того, насколько полна до его емкости автоматически увеличивается. Когда количество записей в хэш -таблице превышает произведение коэффициента нагрузки и текущей емкости, хэш -таблица должна быть перефразирована (то есть, восстановить внутреннюю структуру данных), а реконструкция создается вдвое больше тока. Начальная емкость и коэффициент нагрузки могут быть установлены через конструктор. Начальная емкость по умолчанию составляет 16 записей, максимальная емкость составляет 2^30 записей, а коэффициент загрузки по умолчанию составляет 0,75
Ведро похоже на ведро, которое хранит воду. Начальная емкость по умолчанию составляет 16 единиц воды. Когда вода заливается до 16*0,75, емкость будет расширена сначала до 32 единиц, когда данные добавляются в следующий раз. 0,75 является коэффициентом нагрузки, а начальная емкость и коэффициент нагрузки могут быть установлены при создании ведра. Максимальная пропускная способность ведра составляет 2 единицы воды на мощность 30. Когда количество настройки начальной емкости больше, чем максимальная емкость, максимальная емкость преобладает. При расширении он будет возвращен непосредственно, если он будет больше или равен максимальной емкости.
Ниже приведено часть исходного кода HashMap, который определяет начальную емкость по умолчанию, коэффициент загрузки и некоторые другие константы:
/ ** * Начальная емкость по умолчанию должна быть мощностью 2. */ Статический окончательный int int default_initial_capacity = 1 << 4; // aka 16/ *** Максимальная емкость, если начальная емкость превышает максимальную емкость через параметр конструктора, емкость также будет использоваться в качестве начальной емкости* должна быть мощность 2 и меньше или равна 30 -й мощности 2*/ статическое конечное значение int maximum_capacity = 1 << 30; / *** Коэффициент загрузки по умолчанию может быть указан с помощью конструктора*/ static final float default_load_factor = 0.75f; / *** Пустая таблица массива, когда ведро не инициализировано*/ статическая конечная запись <?,?> [] Empty_table = {}; / *** Ведре, хранит все записи пары клавиш и может быть изменено по мере необходимости, и длина должна быть мощностью 2*/ переходной записи <K, v> [] table = (inpit <K, v> []) empty_table; /*** Количество пар клавишных значений На карте размер будет работать +1 или -1 операция каждый раз, когда он добавляется или удаляется. */ Transient Int Size; /** * Критическое значение значения нагрузки, которое необходимо изменить размер: (емкость * Коэффициент нагрузки). После каждого изменения размера новая емкость будет рассчитана с использованием новой емкости. * @serial */ // if table == empty_table, тогда это начальная емкость, при которой таблица // будет создана при надувании. int порог; / ** * Коэффициент загрузки, если не указан в конструкторе, используется коэффициент загрузки по умолчанию, * * @serial */ final Float LoadFactor; /** * Количество модификаций структуры Hashmap, изменить количество карт в HashMAP или изменить ее внутреннюю структуру при изменении структуры (например, метод * Rechash, восстанавливает внутреннюю структуру данных). Это поле используется в * итераторах, сгенерированных в представлении коллекции HashMap, обрабатываются в быстрый неудачный */Transient Int ModCount;Начальная регулировка производительности коэффициента нагрузки и коэффициента нагрузки
Как правило, коэффициент нагрузки по умолчанию (0,75) ищет компромисс во временных и пространственных затратах. Хотя коэффициент нагрузки слишком высок, он также увеличивает стоимость запроса (это отражается в большинстве операций HashMap, включая операции GET и PLO). При настройке начальной емкости количество записей, необходимых на карте, и коэффициента нагрузки, должно быть принято во внимание, чтобы минимизировать количество операций перефразирования. Если начальная емкость больше, чем максимальное количество записей, деленных на коэффициент нагрузки, операция перефразирования не будет происходить.
Если многие отношения отображения должны храниться в экземпляре HashMap, создание его с достаточно большой начальной емкостью сделает отношения отображения более эффективными по сравнению с выполнением автоматических операций перефразирования по требованию для увеличения емкости таблицы.
Ниже приведен код для восстановления структуры данных HashMap:
void reszize (int newcapacity) {inpit [] oldtable = table; int oldCapacity = oldtable.length; if (OldCapacity == Maximum_Capacity) {// Если емкость достигла максимального предела, затем установите значение нагрузки и верните непосредственно на threshold = integer.max_value; возвращаться; } // Создать новую таблицу для хранения ввода данных [] newtable = new Intry [newCapacity]; // Передача данных из старой таблицы в новую таблицу, этот шаг займет много времени для передачи (Newtable, InithashseDasneededed (NewCapacity)); Таблица = новичок; // Наконец -то установите значение нагрузки следующего resize threshold = (int) math.min (newcapacity * loadfactor, maximum_capacity + 1);}Метод строительства HashMap
Четвертый метод строительства состоит в том, чтобы создать новую HashMap с существующей картой. Поговорим об этом позже. Фактически, первые три метода строительства вызываются в последнем третьем методе с двумя параметрами. Если параметры не передаются, значение по умолчанию используется. Код заключается в следующем:
/** * Конструирует пустой <tt> hashmap </tt> с начальной емкостью по умолчанию * (16) и коэффициентом загрузки по умолчанию (0,75). */ public hashmap () {this (default_initial_capacity, default_load_factor); } /** * Создает пустой <tt> hashmap < /tt> с указанной начальной * емкостью и коэффициентом загрузки по умолчанию (0,75). * * @param initialcapacity Начальная емкость. * @Throws allogalArgumentException, если начальная емкость отрицательна. */ public hashmap (int initialCapacity) {this (initialCapacity, default_load_factor); } /** * Конструирует пустой <tt> hashmap < /tt> с указанным начальным * емкостью и коэффициентом нагрузки. * * @param initialcapacity Начальная емкость * @param LoadFactor Коэффициент нагрузки * @Throws allogalArgumentException Если начальная емкость отрицательна * или коэффициент нагрузки не является положительным */ public hashmap (int initialcapity, float load factor) {if (initialCapacity <0). Забросить NewallalargumentException ("незаконная начальная способность:" +initalcapacity); if (initialCapacity> maximum_capacity) initialCapacity = maximum_capacity; if (loadfactor <= 0 || float.isnan (loadfactor)) бросить новое allosalargumentException («Коэффициент незаконной нагрузки:» +loadfactor); this.LoadFactor = LoadFactor; Порог = начальная капсула; init (); }Как видно из вышеперечисленного, в конструкторе, если начальная емкость больше, чем максимальная емкость, максимальная емкость будет заменена напрямую.
Поместите метод
Далее, давайте посмотрим на более важные части HashMap
/*** На этой карте связаны указанное значение и указанная сборка. Если карта ранее содержала отношение картирования ключа, старое значение было заменено** @param определяет ключ, который будет связан* @param Указывает значение, которое должно быть связано* @return Старое значение, связанное с ключом, если ключ не имеет отношения отображения, оно возвращает NULL (возвращение NULL также может означать, что картирование было связано с ключом)*/ public V PUT) (v value) также может означать, что картирование было связано с ключом). зажигательный (порог); } if (key == null) return putfornullkey (value); int hash = hash (ключ); int i = indexfor (hash, table.length); for (intry <k, v> e = table [i]; e! = null; e = e.next) {объект k; if (e.hash == hash && ((k = e.key) == key || key.equals (k))) {v oldvalue = e.value; e.value = значение; e.recordaccess (это); вернуть OldValue; }} modcount ++; AddEntry (хэш, ключ, значение, i); вернуть ноль; }1. Во -первых, в методе POT сначала определите, находится ли ведро в ненициализированном состоянии по умолчанию. Если он не инициализирован, вызовите метод зажигания для его инициализации, а затем определите, является ли клавиша параметра нулевой. Если он нулевой, вызовите Putfornullkey специально, чтобы поместить данные с ключом как NULL. Метод PutfornullKey на самом деле такой же, как и следующий шаг 3, за исключением того, что место хранения данных по умолчанию данных с ключом в качестве NULL является первым, то есть подписка по умолчанию составляет 0.
2. Если ключ не является нулевым, вызовите метод hash (), чтобы получить хэш -значение ключа. Вы можете рассчитать положение, где ключ может быть помещен в ведро на основе значения хэша и длины ведра.
3. Далее есть атрибут в объекте записи, который может сформировать односторонний связанный список для хранения элементов с таким же значением хэша. Следовательно, когда рассчитывается значение хэша ключа, расположение хранения также будет повторено. Просто судите, является ли элемент местоположения хранилища и следующего списка атрибутов элемента точно таким же, как и значение хэш -клавиши и ключа. Если есть совершенно согласованная, это означает, что он уже существует, замените старое значение и верните старое значение непосредственно в качестве возврата.
4. Увеличьте количество структурных модификаций на 1
5. Вызовите метод AddEntry, чтобы добавить новую пару клавишных значений в HashMap. Метод добавления сначала определяет, превышают ли текущие данные о записи или равны значению нагрузки (емкость коэффициента нагрузки ведра *), а указанное положение ведра не является нулевым. Если это уже больше, и указанная позиция не является нулевой, емкость регулировочного ведра в два раза превышает ток. Емкость корректируемого ведра ссылается на каталог корректировки начальной емкости и коэффициента нагрузки . Поврекайте значение хэша и вычислите место для хранения. Вызовите метод Createentry, чтобы хранить его в ведре
void AddEntry (int hash, k -ключ, v Значение, int bucketIndex) {if ((size> = threshold) && (null! = table [bucketindex])) {resize (2 * table.length); хэш = (null! = ключ)? хэш (ключ): 0; bucketindex = indexfor (hash, table.length); } createEntry (хэш, ключ, значение, bucketIndex); } void createEntry (int hash, k key, v value, int bucketindex) {inpit <k, v> e = table [bucketindex]; Таблица [BucketIndex] = Новая запись <> (хэш, ключ, значение, E); размер ++; } /*** Метод конструктора входа для создания новой записи. */ Inpit (int H, K K, V V, intry <K, v> n) {value = v; Next = N; Key = k; хэш = h; }6. В методе Createentry сначала получите запись в указанном месте, а затем сгенерируйте новую запись. При создании записи храните исходную запись в следующей свойстве недавно сгенерированной записи (см. Метод входа) и замените запись в указанном месте на недавно сгенерированную.
Поскольку при добавлении новых записей необходимо рассчитать значение хэша, и когда длина недостаточно, необходимо регулировать длину. Когда в расчетном месте хранения есть элементы, необходимо хранить связанный список, поэтому эффективность использования HashMap для добавления новых операций не слишком высока.
Получить метод
Во -первых, давайте посмотрим на исходный код метода GET:
/*** Возвращает значение, отображаемое указанным ключом; Если эта карта не содержит никаких отношений со отображением для этого ключа, она возвращает NULL * Возвращение нулевого значения, не обязательно означает, что карта не содержит отображения ключа, а также может изменить отображение на нулевое. Вы можете использовать операцию «Содержит», чтобы различать эти две ситуации * @see #put (object, object) */ public v get (cheobe key) {if (key == null) return getFornullKey (); Inpit <K, v> intry = getEntry (key); return null == запись? null: entry.getValue (); } Окончательная запись <K, v> getEntry (ject -key) {if (size == 0) {return null; } int hash = (key == null)? 0: хэш (ключ); for (inpit <k, v> e = table [indexfor (hash, table.length)]; e! = null; e = e.next) {объект k; if (e.hash == hash && ((k = e.key) == key || (key! = null && key.equals (k))) return e; } return null;}Метод GET прост в реализации, и ниже приведены несколько шагов:
Посмотрев на исходный код GET, мы можем обнаружить, что метод GET вычисляет местоположение хранилища посредством хэш -значения ключа и длины ведра. По сути, вы можете найти элемент, который вы ищете. Даже если вы пересекаете несколько ключей с повторными значениями хэша, это очень быстро. Поскольку хеш -ценность относительно уникальна, HashMap очень быстрая для поиска.
Пользовательский объект как ключ к HashMap
класс пользователь {// идентификационный номер защищен int idnumber; public user (int id) {idnumber = id; }} public class testuser {public static void main (string [] args) {map <user, string> map = new hashmap <user, string> (); for (int i = 0; i <5; i ++) {map.put (новый пользователь (i), "name:"+i); } System.out.println ("Имя пользователя3:" + map.get (новый пользователь (3))); }} Output: user3 name: nullКак упомянуто выше, при использовании пользовательского экземпляра класса пользователя в качестве объекта Hashmap имя пользователя3 не может быть найдено при печати, поскольку класс пользователя автоматически наследует объект базового класса, поэтому метод объекта HashCode будет автоматически использован для генерации значения хэша, и он использует адрес объекта для расчета значения хэша по умолчанию. Следовательно, значение хэша первого экземпляра, сгенерированное новым пользователем (3), отличается от хэш -значения второго сгенерированного экземпляра. Однако, если вам нужно только просто переопределить метод хэшкода, он не будет работать должным образом, если вы не переопределяете метод Equals одновременно, он также является частью объекта. Hashmap использует equals (), чтобы определить, такой же, как текущий ключ, как ключи, присутствующие в таблице. Вы можете обратиться к методу получить или положить выше.
Правильный метод equals () должен соответствовать следующим 5 условиям: --- См. «Мысли по программированию Java» -стр. 489
Опять же : объект по умолчанию. Equals () - это просто адрес объекта сравнения, поэтому один новый пользователь (3) не равняется другому новому пользователю (3). Поэтому, если вы хотите использовать свой собственный класс в качестве ключа к HashMap, вы должны перегрузить как hashcode (), так и equals ().
Следующий код работает нормально:
класс пользователь {// идентификационный номер защищен int idnumber; public user (int id) {idnumber = id; } @Override public int hashcode () {return idnumber; } @Override public boolean equals (Object obj) {return obj exancemor user && (idnumber == ((пользователь) obj) .idnumber); }} public class testuser {public static void main (string [] args) {map <user, string> map = new hashmap <user, string> (); for (int i = 0; i <5; i ++) {map.put (новый пользователь (i), "name:"+i); } System.out.println ("Имя пользователя3:" + map.get (новый пользователь (3))); }} Вывод: Имя пользователя3: Имя: 3Вышеуказанное просто возвращает Idnumber как единственную дискриминацию в хашкоде, и пользователи также могут реализовать свои собственные методы в соответствии с их собственным бизнесом. В методе equals экземпляр незаметно проверит, является ли объект нулевым. Если параметр слева от экземпляра является нулевым, false будет возвращен. Если параметр equals () не является нулевым, а тип верен, сравнение будет основано на фактическом идентификаторе в каждом объекте. Как видно из вывода, текущий метод верен.
См.
"Java программирование мыслей"
JDK 1.6 РУКОВОДСТВО ПОМОЩИ КИТА
Выше всего содержание этой статьи. Я надеюсь, что содержание этой статьи поможет всем, кто учится или работа. Я также надеюсь поддержать Wulin.com больше!