Перейти к контенту

Подробно про HashMap в Java

HashMap в Java — это реализация структуры данных хэш-таблицы (пары ключ-значение, словарь) интерфейса Map, являющейся частью структуры Java Collections.

Реализации HashMap в Java Collections Framework

HashMap имеет следующие особенности:

 

  • Коэффициент загрузки по умолчанию и начальная мощность 0,75 и 16 соответственно. Их значения важны для производительности HashMap, поскольку они могут оптимизировать производительность итераций и количество операций изменения размера и повторного хеширования.
  • Нет гарантий порядка итераций.
  • Производительность итерации зависит от начальной емкости (количества сегментов) плюс количества записей. Таким образом, очень важно не устанавливать слишком высокую начальную мощность (или слишком низкий коэффициент загрузки).
  • Никаких повторяющихся ключей. Разрешает один нулевой ключ и несколько нулевых значений.
  • Проблема коллизии хэша решена за счет использования древовидной структуры данных, начиная с Java 8, для обеспечения отдельной цепочки.
  • Предлагает постоянное время O (1) в среднем и линейное время O (n) в худшем случае производительность для основных операций, таких как получение, размещение и удаление.
  • Чем меньше повторяющихся хэш-кодов, тем выше прирост производительности для вышеуказанных операций.
  • Ключевые объекты сравниваются на основе их равенства и реализации hashCode.
  • Объекты значений сравниваются на основе реализации их метода равенства.
  • HashMap не является потокобезопасным, поскольку это несинхронизированная реализация.
  • В многопоточном окружении хотя бы один поток изменяет карту, она должна быть синхронизирована извне.

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

HashMap против LinkedHashMap и TreeMap

HashMap не имеет гарантий упорядочивания и работает быстрее, чем TreeMap (постоянное время по сравнению с временем журнала для большинства операций)

LinkedHashMap обеспечивает итерацию с упорядоченной вставкой и работает почти так же быстро, как HashMap.

TreeMap обеспечивает итерацию по порядку значений. TreeMap можно использовать для сортировки HashMap или LinkedHashMap

Объявление HashMap

В результате иерархии классов вы можете объявить HashMap следующими способами:

@Test
public void declare() {
    Map<String, Integer> map1 = new HashMap<>();
    assertThat(map1).isInstanceOf(HashMap.class);

    HashMap<String, Integer> map2 = new HashMap<>();
}

Создание и инициализация

Предоставьте фабричный метод Map.of или Map.ofEntries, начиная с Java 9, в конструктор HashMap (Map) для создания и инициализации HashMap в одной строке во время создания.

@Test
public void initInOneLineWithFactoryMethods() {
    // create and initialize a HashMap from Java 9+ Map.of
    Map<String, Integer> map1 = new HashMap<>((Map.of("k1", 1, "k3", 2, "k2", 3)));
    assertThat(map1).hasSize(3);

    // create and initialize a HashMap from Java 9+ Map.ofEntries
    Map<String, Integer> map2 = new HashMap<>(Map.ofEntries(Map.entry("k4", 4), Map.entry("k5", 5)));
    assertThat(map2).hasSize(2);
}

Вы также можете инициализировать HashMap после времени создания, используя put, Java 8+ putIfAbsent, putAll.

@Test
public void initializeWithPutIfAbsent() {
    // Create a new HashMap
    Map<String, Integer> map = new HashMap<>();

    // Add elements to HashMap
    map.putIfAbsent("k1", 1);
    map.putIfAbsent("k2", 2);
    map.putIfAbsent("k3", 3);

    // Can add null key and value
    map.putIfAbsent(null, 4);
    map.putIfAbsent("k4", null);

    // Duplicate key will be ignored
    map.putIfAbsent("k1", 10);
    assertThat(map).hasSize(5);

    // Порядок вывода будет варьироваться, так как HashMap не зарезервирован порядок вставки
    System.out.println(map);
}

Итерация HashMap

Вы можете перебирать пары ключ-значение HashMap, используя Java 8+ forEach (BiConsumer).

@Test
public void iterateOverKeyValuePairs() {
    Map<String, Integer> map = new HashMap<>(Map.of("k1", 1, "k2", 2));
    map.forEach((k, v) -> System.out.printf("%s=%d ", k, v));
}

Итерировать по HashMap keySet() или values() с Java 8+ forEach(Consumer).

@Test
public void iterateOverKeySet() {
    Map<String, Integer> map = new HashMap<>(Map.of("k1", 1, "k2", 2));
    map.keySet().forEach(k -> System.out.printf("%s ", k));
}

Получение и фильтрация

Используйте entrySet(), keySet(), values(), чтобы получить набор записей сопоставления ключ-значение, набор ключей и набор значений соответственно.

@Test
public void retrieve() {
    Map<String, Integer> hashMap = new HashMap<>(Map.of("k1", 1, "k2", 2));

    Set<Map.Entry<String, Integer>> entrySet = hashMap.entrySet();
    assertThat(entrySet).contains(Map.entry("k1", 1), Map.entry("k2", 2));

    Set keySet = hashMap.keySet();
    assertThat(keySet).contains("k1", "k2");

    Collection values = hashMap.values();
    assertThat(values).contains(1, 2);
}

Получить значение по указанному ключу с помощью get(key).

@Test
public void getValueByKey() {
    Map<String, Integer> map = new HashMap<>(Map.of("k1", 1, "k2", 2));
    int value = map.get("k1");

    assertThat(value).isEqualTo(1);
}

Фильтрация ключей или значений HashMap с помощью Java 8+ Stream API.

@Test
public void filter() {
    Map<String, Integer> map = new HashMap<>(Map.of("k1", 1, "k2", 2));
    Integer[] arr = map.values().stream().filter(v -> v > 1).toArray(Integer[]::new);

    assertThat(arr).contains(2);
}

Добавление, обновление и удаление

HashMap предоставляет методы containsKey (ключ), containsValue (значение), put (ключ, значение), replace (ключ, значение) и remove (ключ), чтобы проверить, содержит ли HashMap указанный ключ или значение, чтобы добавить новый ключ. пара значений, обновить значение по ключу, удалить запись по ключу соответственно.

@Test
public void containsPutReplaceRemove() {
    Map<String, Integer> map = new HashMap<>(Map.of("k1", 1, "k2", 2));

    boolean containedKey = map.containsKey("k1");
    assertThat(containedKey).isTrue();

    boolean containedValue = map.containsValue(2);
    assertThat(containedValue).isTrue();
    
    map.put("k3", 3);
    assertThat(map).hasSize(3);
    
    map.replace("k1", 10);
    assertThat(map).contains(Map.entry("k1", 10), Map.entry("k2", 2), Map.entry("k3", 3));
    
    map.remove("k3");
    assertThat(map).contains(Map.entry("k1", 10), Map.entry("k2", 2));
}

Сравнение объектов в HashMap

Внутренне основные операции HashMap, такие как containsKey, containsValue, put, putIfAbsent, replace и remove, работают на основе сравнения объектов элементов, которые зависят от их равенства и реализации hashCode.

В следующем примере ожидаемые результаты не достигаются из-за отсутствия реализации equals и hashCode для определенных пользователем объектов.

hashMap.containsKey(new Category(1, "c1")) and hashMap.containsValue(new Book(1, "a")) should return true but false
hashMap.put(new Category(1, "c1"), new Book(1, "a")) should not but success
hashMap.replace(new Category(1, "c1"), new Book(2, "a")) and hashMap.remove(new Category(1, "c1")) should success but not

@Test
public void objectsComparingProblem(){
    Map<Category, Book> hashMap = new HashMap<>();

    hashMap.put(new Category(1, "c1"), new Book(1, "a"));

    boolean containedKey = hashMap.containsKey(new Category(1, "c1"));
    assertThat(containedKey).isFalse();

    boolean containedValue = hashMap.containsValue(new Book(1, "a"));
    assertThat(containedValue).isFalse();

    hashMap.put(new Category(1, "c1"), new Book(1, "a"));
    assertThat(hashMap).hasSize(2);

    hashMap.remove(new Category(1, "c1"));
    assertThat(hashMap).hasSize(2);
}

static class Category {
    int id;
    String name;

    Category(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

static class Book {
    int id;
    String title;

    Book(int id, String title) {
        this.id = id;
        this.title = title;
    }
}

Вы можете решить указанную выше проблему, переопределив equals и hashCode, как показано в примере ниже.

@Test
public void objectsComparingFixed(){
    Map<CategoryFixed, BookFixed> hashMap = new HashMap<>();

    hashMap.put(new CategoryFixed(1, "c1"), new BookFixed(1, "b1"));

    boolean containedKey = hashMap.containsKey(new CategoryFixed(1, "c1"));
    assertThat(containedKey).isTrue();

    boolean containedValue = hashMap.containsValue(new BookFixed(1, "b1"));
    assertThat(containedValue).isTrue();

    hashMap.put(new CategoryFixed(1, "c1"), new BookFixed(1, "b1"));
    assertThat(hashMap).hasSize(1);

    BookFixed previousValue = hashMap.replace(new CategoryFixed(1, "c1"), new BookFixed(2, "b1"));
    assertThat(previousValue).isNotNull();

    hashMap.remove(new CategoryFixed(1, "c1"));
    assertThat(hashMap).hasSize(0);
}

static class CategoryFixed {
    int id;
    String name;

    CategoryFixed(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CategoryFixed that = (CategoryFixed) o;
        return id == that.id &&
                Objects.equals(name, that.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

static class BookFixed {
    int id;
    String title;

    BookFixed(int id, String title) {
        this.id = id;
        this.title = title;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BookFixed bookFixed = (BookFixed) o;
        return id == bookFixed.id &&
                Objects.equals(title, bookFixed.title);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, title);
    }
}

Сортировка HashMap

В Java нет прямого API для сортировки HashMap. Однако вы можете сделать это через TreeMap, TreeSet и ArrayList вместе с Comparable и Comparator.

В следующем примере используются статические методы comparingByKey (Comparator) и comparingByValue (Comparator) для Map.Entry для сортировки ArrayList по ключам и по значениям соответственно. Этот ArrayList создается и инициализируется из entrySet() HashMap.

@Test
public void sortByKeysAndByValues_WithArrayListAndComparator() {
Map.Entry e1 = Map.entry("k1", 1);
Map.Entry e2 = Map.entry("k2", 20);
Map.Entry e3 = Map.entry("k3", 3);

Map map = new HashMap<>(Map.ofEntries(e3, e1, e2));

List> arrayList1 = new ArrayList<>(map.entrySet());
arrayList1.sort(comparingByKey(Comparator.naturalOrder()));
assertThat(arrayList1).containsExactly(e1, e2, e3);

List> arrayList2 = new ArrayList<>(map.entrySet());
arrayList2.sort(comparingByValue(Comparator.reverseOrder()));
assertThat(arrayList2).containsExactly(e2, e3, e1);
}

Ниже представлен полный пример исходного кода.

package com.hellokoding.java.collections;

import org.junit.Test;

import java.util.*;

import static java.util.Map.Entry.comparingByKey;
import static java.util.Map.Entry.comparingByValue;
import static org.assertj.core.api.Assertions.assertThat;

public class HashMapTest {
@Test
public void declare() {
Map map1 = new HashMap<>();
assertThat(map1).isInstanceOf(HashMap.class);

HashMap map2 = new HashMap<>();
}

@Test
public void initInOneLineWithFactoryMethods() {
// create and initialize a HashMap from Java 9+ Map.of
Map map1 = new HashMap<>((Map.of("k1", 1, "k3", 2, "k2", 3)));
assertThat(map1).hasSize(3);

// create and initialize a HashMap from Java 9+ Map.ofEntries
Map map2 = new HashMap<>(Map.ofEntries(Map.entry("k4", 4), Map.entry("k5", 5)));
assertThat(map2).hasSize(2);
}

@Test
public void initializeWithPutIfAbsent() {
// Create a new HashMap
Map map = new HashMap<>();

// Add elements to HashMap
map.putIfAbsent("k1", 1);
map.putIfAbsent("k2", 2);
map.putIfAbsent("k3", 3);

// Can add null key and value
map.putIfAbsent(null, 4);
map.putIfAbsent("k4", null);

// Duplicate key will be ignored
map.putIfAbsent("k1", 10);
assertThat(map).hasSize(5);

// The output ordering will be vary as HashMap is not reserved the insertion order
System.out.println(map);
}

@Test
public void iterateOverKeyValuePairs() {
Map map = new HashMap<>(Map.of("k1", 1, "k2", 2));
map.forEach((k, v) -> System.out.printf("%s=%d ", k, v));
}

@Test
public void iterateOverKeySet() {
Map map = new HashMap<>(Map.of("k1", 1, "k2", 2));
map.values().forEach(k -> System.out.printf("%s ", k));
}

@Test
public void retrieve() {
Map hashMap = new HashMap<>(Map.of("k1", 1, "k2", 2));

Set> entrySet = hashMap.entrySet();
assertThat(entrySet).contains(Map.entry("k1", 1), Map.entry("k2", 2));

Set keySet = hashMap.keySet();
assertThat(keySet).contains("k1", "k2");

Collection values = hashMap.values();
assertThat(values).contains(1, 2);
}

@Test
public void getValueByKey() {
Map map = new HashMap<>(Map.of("k1", 1, "k2", 2));
int value = map.get("k1");

assertThat(value).isEqualTo(1);
}

@Test
public void filter() {
Map map = new HashMap<>(Map.of("k1", 1, "k2", 2));
Integer[] arr = map.values().stream().filter(v -> v > 1).toArray(Integer[]::new);
assertThat(arr).contains(2);
}

@Test
public void containsPutReplaceRemove() {
Map map = new HashMap<>(Map.of("k1", 1, "k2", 2));

boolean containedKey = map.containsKey("k1");
assertThat(containedKey).isTrue();

boolean containedValue = map.containsValue(2);
assertThat(containedValue).isTrue();

map.put("k3", 3);
assertThat(map).hasSize(3);

map.replace("k1", 10);
assertThat(map).contains(Map.entry("k1", 10), Map.entry("k2", 2), Map.entry("k3", 3));

map.remove("k3");
assertThat(map).contains(Map.entry("k1", 10), Map.entry("k2", 2));
}

@Test
public void objectsComparingProblem(){
Map hashMap = new HashMap<>();

hashMap.put(new Category(1, "c1"), new Book(1, "b1"));

boolean containedKey = hashMap.containsKey(new Category(1, "c1"));
assertThat(containedKey).isFalse();

boolean containedValue = hashMap.containsValue(new Book(1, "b1"));
assertThat(containedValue).isFalse();

hashMap.put(new Category(1, "c1"), new Book(1, "b1"));
assertThat(hashMap).hasSize(2);

Book previousValue = hashMap.replace(new Category(1, "c1"), new Book(2, "b1"));
assertThat(previousValue).isNull();

hashMap.remove(new Category(1, "c1"));
assertThat(hashMap).hasSize(2);
}

static class Category {
int id;
String name;

Category(int id, String name) {
this.id = id;
this.name = name;
}
}

static class Book {
int id;
String title;

Book(int id, String title) {
this.id = id;
this.title = title;
}
}

@Test
public void objectsComparingFixed(){
Map hashMap = new HashMap<>();

hashMap.put(new CategoryFixed(1, "c1"), new BookFixed(1, "b1"));

boolean containedKey = hashMap.containsKey(new CategoryFixed(1, "c1"));
assertThat(containedKey).isTrue();

boolean containedValue = hashMap.containsValue(new BookFixed(1, "b1"));
assertThat(containedValue).isTrue();

hashMap.put(new CategoryFixed(1, "c1"), new BookFixed(1, "b1"));
assertThat(hashMap).hasSize(1);

BookFixed previousValue = hashMap.replace(new CategoryFixed(1, "c1"), new BookFixed(2, "b1"));
assertThat(previousValue).isNotNull();

hashMap.remove(new CategoryFixed(1, "c1"));
assertThat(hashMap).hasSize(0);
}

static class CategoryFixed {
int id;
String name;

CategoryFixed(int id, String name) {
this.id = id;
this.name = name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CategoryFixed that = (CategoryFixed) o;
return id == that.id &&
Objects.equals(name, that.name);
}

@Override
public int hashCode() {
return Objects.hash(id, name);
}
}

static class BookFixed {
int id;
String title;

BookFixed(int id, String title) {
this.id = id;
this.title = title;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BookFixed bookFixed = (BookFixed) o;
return id == bookFixed.id &&
Objects.equals(title, bookFixed.title);
}

@Override
public int hashCode() {
return Objects.hash(id, title);
}
}

@Test
public void sortByKeysAndByValues_WithArrayListAndComparator() {
Map.Entry e1 = Map.entry("k1", 1);
Map.Entry e2 = Map.entry("k2", 20);
Map.Entry e3 = Map.entry("k3", 3);

Map map = new HashMap<>(Map.ofEntries(e3, e1, e2));

List> arrayList1 = new ArrayList<>(map.entrySet());
arrayList1.sort(comparingByKey(Comparator.naturalOrder()));
assertThat(arrayList1).containsExactly(e1, e2, e3);

List> arrayList2 = new ArrayList<>(map.entrySet());
arrayList2.sort(comparingByValue(Comparator.reverseOrder()));
assertThat(arrayList2).containsExactly(e2, e3, e1);
}
}

Оцени статью

Средняя оценка / 5. Количество голосов:

Спасибо, помогите другим - напишите комментарий, добавьте информации к статье.

Или поделись статьей

Видим, что вы не нашли ответ на свой вопрос.

Помогите улучшить статью.

 

Пока нет комментариев.

Добавить комментарий

Ваш e-mail не будет опубликован.

СайдбарКомментарии (0)