Java HashMap – HashMap в Java

Java HashMap – один из самых популярных классов коллекций в Java. Java HashMap – это реализация на основе хэш-таблицы. HashMap в Java расширяет класс AbstractMap, реализующий интерфейс Map.

Java HashMap

Некоторые важные моменты о HashMap в Java;

  1. Java HashMap позволяет использовать null в качестве ключа и значений.
  2. HashMap не является упорядоченной коллекцией. Вы можете перебирать записи HashMap через набор ключей, но их порядок не гарантирован в соответствии с порядком их добавления в HashMap.
  3. HashMap почти аналогичен Hashtable, за исключением того, что он несинхронизирован и позволяет использовать null в качестве ключа и значений.
  4. HashMap использует свой внутренний класс Node<K,V> для хранения записей карты.
  5. HashMap сохраняет записи в нескольких однонаправленных связанных списках, называемых корзинами. Количество бакетов по умолчанию – 16, и оно всегда является степенью 2.
  6. HashMap использует методы hashCode() и equals() для ключей при операциях get и put. Поэтому объект ключа HashMap должен предоставлять хорошую реализацию этих методов. Вот почему для ключей лучше всего подходят неизменяемые классы, например, String и Integer.
  7. Java HashMap не является потокобезопасным, для многопоточной среды следует использовать класс ConcurrentHashMap или получить синхронизированную карту с помощью метода Collections.synchronizedMap().

Конструкторы Java HashMap

Java HashMap предоставляет четыре конструктора.

  1. public HashMap(): Самый часто используемый конструктор HashMap. Этот конструктор создает пустой HashMap с начальной емкостью 16 и коэффициентом загрузки 0.75
  2. public HashMap(int initialCapacity): Этот конструктор HashMap используется для указания начальной емкости и коэффициента загрузки 0.75. Это полезно для избежания повторного хеширования, если вы знаете количество отображений, которые будут храниться в HashMap.
  3. public HashMap(int initialCapacity, float loadFactor): Этот конструктор HashMap создает пустой HashMap с указанной начальной емкостью и коэффициентом загрузки. Вы можете использовать это, если знаете максимальное количество отображений, которые будут храниться в HashMap. В общих сценариях вы должны избегать этого, потому что коэффициент загрузки 0.75 предлагает хороший компромисс между затратами на пространство и время.
  4. public HashMap(Map<? extends K, ? extends V> m): Создает карту с теми же отображениями, что и указанная карта, и коэффициентом загрузки 0.75

Пример конструкторов Java HashMap

Ниже приведен фрагмент кода, показывающий пример использования всех вышеуказанных конструкторов HashMap.

Map<String, String> map1 = new HashMap<>();

Map<String, String> map2 = new HashMap<>(2^5);

Map<String, String> map3 = new HashMap<>(32,0.80f);

Map<String,String> map4 = new HashMap<>(map1);

Методы Java HashMap

Давайте рассмотрим важные методы HashMap в Java.

  1. public void clear(): Этот метод HashMap удалит все отображения, и HashMap станет пустым.
  2. public boolean containsKey(Object key): Этот метод возвращает «true», если ключ существует, в противном случае возвращает «false».
  3. public boolean containsValue(Object value): Этот метод HashMap возвращает true, если значение существует, иначе false.
  4. public Set<Map.Entry<K,V>> entrySet(): Этот метод возвращает представление Set отображений HashMap. Этот набор поддерживается картой, поэтому изменения в карте отражаются в наборе и наоборот.
  5. public V get(Object key): Возвращает значение, сопоставленное указанному ключу, или null, если для ключа нет отображения.
  6. public boolean isEmpty(): Утилитарный метод, возвращающий true, если отображений ключ-значение отсутствуют.
  7. public Set<K> keySet(): Возвращает представление Set ключей, содержащихся в этой карте. Набор поддерживается картой, поэтому изменения в карте отражаются в наборе и наоборот.
  8. public V put(K key, V value): Связывает указанное значение с указанным ключом в этой карте. Если карта ранее содержала отображение для ключа, старое значение заменяется.
  9. public void putAll(Map<? extends K, ? extends V> m): Копирует все отображения из указанной карты в эту карту. Эти отображения заменят любые отображения, которые уже есть в этой карте для любых ключей, которые в настоящее время есть в указанной карте.
  10. public V remove(Object key): Удаляет отображение для указанного ключа из этой карты, если оно присутствует.
  11. public int size(): Возвращает количество отображений ключ-значение в этой карте.
  12. public Collection<V> values(): Возвращает представление коллекции значений, содержащихся в этой карте. Коллекция поддерживается картой, поэтому изменения в карте отражаются в коллекции, и наоборот.

В HashMap в Java 8 было введено много новых методов.

  1. public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction): Если указанный ключ еще не связан с каким-либо значением (или связан с null), этот метод пытается вычислить его значение с использованием заданной функции отображения и вводит его в HashMap, если Null.
  2. public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction): Если значение для указанного ключа присутствует и не равно null, пытается вычислить новое отображение по заданному ключу и его текущему отображенному значению.
  3. public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction): Этот метод HashMap пытается вычислить отображение для указанного ключа и его текущего отображенного значения.
  4. public void forEach(BiConsumer<? super K, ? super V> action): Этот метод выполняет заданное действие для каждой записи в этой карте.
  5. public V getOrDefault(Object key, V defaultValue): То же, что и get, за исключением того, что возвращается defaultValue, если для указанного ключа не найдено отображение.
  6. public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction): Если указанный ключ еще не связан с значением или связан с null, то ассоциирует его с заданным ненулевым значением. В противном случае заменяет связанное значение результатами заданной функции remapping, или удаляет, если результат null.
  7. public V putIfAbsent(K key, V value): Если указанный ключ еще не связан со значением (или отображен как null), то ассоциирует его с заданным значением и возвращает null, в противном случае возвращает текущее значение.
  8. public boolean remove(Object key, Object value): Удаляет запись для указанного ключа только в том случае, если она в настоящее время отображена на указанное значение.
  9. public boolean replace(K key, V oldValue, V newValue): Заменяет запись для указанного ключа только в том случае, если в настоящее время она отображена на указанное значение.
  10. public V replace(K key, V value): Заменяет запись для указанного ключа только в том случае, если в настоящее время она отображена на какое-то значение.
  11. public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function): Заменяет значение каждой записи результатом вызова заданной функции для этой записи.

Пример Java HashMap

Вот простая программа на Java для общих методов использования HashMap.

package com.journaldev.examples;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class HashMapExample {

	public static void main(String[] args) {

		Map<String, String> map = new HashMap<>();

		map.put("1", "1"); // put example
		map.put("2", "2");
		map.put("3", "3");
		map.put("4", null); // null value
		map.put(null, "100"); // null key

		String value = map.get("3"); // get example
		System.out.println("Key = 3, Value = " + value);

		value = map.getOrDefault("5", "Default Value");
		System.out.println("Key = 5, Value=" + value);

		boolean keyExists = map.containsKey(null);
		boolean valueExists = map.containsValue("100");

		System.out.println("keyExists=" + keyExists + ", valueExists=" + valueExists);

		Set<Entry<String, String>> entrySet = map.entrySet();
		System.out.println(entrySet);

		System.out.println("map size=" + map.size());

		Map<String, String> map1 = new HashMap<>();
		map1.putAll(map);
		System.out.println("map1 mappings= " + map1);

		String nullKeyValue = map1.remove(null);
		System.out.println("map1 null key value = " + nullKeyValue);
		System.out.println("map1 after removing null key = " + map1);

		Set<String> keySet = map.keySet();
		System.out.println("map keys = " + keySet);

		Collection<String> values = map.values();
		System.out.println("map values = " + values);

		map.clear();
		System.out.println("map is empty=" + map.isEmpty());

	}

}

Ниже приведен вывод приведенной выше программы на примере Java HashMap.

Key = 3, Value = 3
Key = 5, Value=Default Value
keyExists=true, valueExists=true
[null=100, 1=1, 2=2, 3=3, 4=null]
map size=5
map1 mappings= {null=100, 1=1, 2=2, 3=3, 4=null}
map1 null key value = 100
map1 after removing null key = {1=1, 2=2, 3=3, 4=null}
map keys = [null, 1, 2, 3, 4]
map values = [100, 1, 2, 3, null]
map is empty=true

Как работает HashMap в Java?

HashMap в Java использует свой внутренний класс Node<K,V> для хранения отображений. HashMap работает на алгоритме хеширования и использует методы hashCode() и equals() для операций получения и добавления. HashMap использует односвязанный список для хранения элементов, которые называются блоками или корзинами. При вызове метода put, hashCode ключа используется для определения корзины, которая будет использоваться для хранения отображения. После того как корзина идентифицирована, hashCode используется для проверки наличия ключа с тем же hashCode или нет. Если существующий ключ с тем же hashCode уже существует, то используется метод equals() для ключа. Если equals возвращает true, то значение перезаписывается, в противном случае создается новое отображение в этой корзине односвязанного списка. Если нет ключа с тем же hashCode, то отображение вставляется в корзину. Для операции получения в HashMap снова используется hashCode ключа для определения корзины, в которой нужно искать значение. После идентификации корзины, происходит обход записей для поиска записи с использованием методов hashCode и equals. Если совпадение найдено, возвращается значение, в противном случае возвращается null. Есть много других вещей, таких как алгоритм хеширования для получения корзины для ключа, перехеширование отображений и т. д. Но для нашей работы просто помните, что операции в HashMap работают с ключом, и хорошая реализация методов hashCode и equals требуется для избежания нежелательного поведения. Ниже изображено объяснение операций get и put. Рекомендуемое чтение: Важность методов hashCode и equals в Java

Java HashMap Фактор Загрузки

Фактор загрузки используется для определения, когда произойдет перехеширование HashMap и размер корзины будет увеличен. Значение по умолчанию для корзины или емкости составляет 16, а фактор загрузки – 0,75. Порог для перехеширования рассчитывается путем умножения емкости и фактора загрузки. Таким образом, значение порога по умолчанию будет 12. Поэтому, когда в HashMap будет более 12 отображений, он будет перехеширован, и количество контейнеров увеличится до следующего степени 2, то есть 32. Обратите внимание, что емкость HashMap всегда является степенью 2. Фактор загрузки по умолчанию 0,75 обеспечивает хороший компромисс между пространственной и временной сложностью. Но вы можете установить его в различные значения в зависимости от ваших потребностей. Если вы хотите сэкономить место, то можете увеличить его значение до 0,80 или 0,90, но операции получения/установки будут занимать больше времени.

Java HashMap keySet

Метод keySet Java HashMap возвращает представление набора ключей в HashMap. Это представление набора поддерживается HashMap, и любые изменения в HashMap отражаются в Set и наоборот. Ниже приведена простая программа, демонстрирующая примеры keySet HashMap и что делать, если вам нужен keySet, не поддерживаемый картой.

package com.journaldev.examples;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class HashMapKeySetExample {

	public static void main(String[] args) {

		Map<String, String> map = new HashMap<>();
		map.put("1", "1");
		map.put("2", "2");
		map.put("3", "3");

		Set<String> keySet = map.keySet();
		System.out.println(keySet);

		map.put("4", "4");
		System.out.println(keySet); // keySet is backed by Map

		keySet.remove("1");
		System.out.println(map); // map is also modified

		keySet = new HashSet<>(map.keySet()); // copies the key to new Set
		map.put("5", "5");
		System.out.println(keySet); // keySet is not modified
	}

}

Вывод вышеуказанной программы покажет, что keySet поддерживается картой.

[1, 2, 3]
[1, 2, 3, 4]
{2=2, 3=3, 4=4}
[2, 3, 4]

Java HashMap значения

Метод Java HashMap values возвращает представление коллекции значений в Map. Эта коллекция поддерживается HashMap, поэтому любые изменения в HashMap будут отражаться в коллекции значений и наоборот. Простой пример ниже подтверждает это поведение коллекции значений HashMap.

package com.journaldev.examples;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class HashMapValuesExample {

	public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		map.put("1", "1");
		map.put("2", "2");
		map.put("3", null);
		map.put("4", null);
		map.put(null, "100");

		Collection<String> values = map.values();
		System.out.println("map values = " + values);

		map.remove(null);
		System.out.println("map values after removing null key = " + values);

		map.put("5", "5");
		System.out.println("map values after put = " + values);

		System.out.println(map);
		values.remove("1"); // changing values collection
		System.out.println(map); // updates in map too

	}

}

Результат выполнения вышеуказанной программы представлен ниже.

map values = [100, 1, 2, null, null]
map values after removing null key = [1, 2, null, null]
map values after put = [1, 2, null, null, 5]
{1=1, 2=2, 3=null, 4=null, 5=5}
{2=2, 3=null, 4=null, 5=5}

Java HashMap entrySet

Метод Java HashMap entrySet возвращает представление множества отображений. Этот entrySet поддерживается HashMap, поэтому любые изменения в отображении отразятся в entry set и наоборот. Рассмотрим пример программы для примера использования entrySet в HashMap.

package com.journaldev.examples;

import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class HashMapEntrySetExample {

	public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		map.put("1", "1");
		map.put("2", null);
		map.put(null, "100");

		Set<Entry<String,String>> entrySet = map.entrySet();
		Iterator<Entry<String, String>> iterator = entrySet.iterator();
		Entry<String, String> next = null;
		
		System.out.println("map before processing = "+map);
		System.out.println("entrySet before processing = "+entrySet);
		while(iterator.hasNext()){
			next = iterator.next();
			System.out.println("Processing on: "+next.getValue());
			if(next.getKey() == null) iterator.remove();
		}
		
		System.out.println("map after processing = "+map);
		System.out.println("entrySet after processing = "+entrySet);
		
		Entry<String, String> simpleEntry = new AbstractMap.SimpleEntry<String, String>("1","1");
		entrySet.remove(simpleEntry);
		System.out.println("map after removing Entry = "+map);
		System.out.println("entrySet after removing Entry = "+entrySet);
	}

}

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

map before processing = {null=100, 1=1, 2=null}
entrySet before processing = [null=100, 1=1, 2=null]
Processing on: 100
Processing on: 1
Processing on: null
map after processing = {1=1, 2=null}
entrySet after processing = [1=1, 2=null]
map after removing Entry = {2=null}
entrySet after removing Entry = [2=null]

Java HashMap putIfAbsent

A simple example for HashMap putIfAbsent method introduced in Java 8.

package com.journaldev.examples;

import java.util.HashMap;
import java.util.Map;

public class HashMapPutIfAbsentExample {

	public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		map.put("1", "1");
		map.put("2", null);
		map.put(null, "100");

		System.out.println("map before putIfAbsent = "+map);
		String value = map.putIfAbsent("1", "4");
		System.out.println("map after putIfAbsent = "+map);
		System.out.println("putIfAbsent returns: "+value);
		
		System.out.println("map before putIfAbsent = "+map);
		value = map.putIfAbsent("3", "3");
		System.out.println("map after putIfAbsent = "+map);
		System.out.println("putIfAbsent returns: "+value);
	}

}

Результат выполнения вышеуказанной программы:

map before putIfAbsent = {null=100, 1=1, 2=null}
map after putIfAbsent = {null=100, 1=1, 2=null}
putIfAbsent returns: 1
map before putIfAbsent = {null=100, 1=1, 2=null}
map after putIfAbsent = {null=100, 1=1, 2=null, 3=3}
putIfAbsent returns: null

Java HashMap forEach

Метод forEach в HashMap был представлен в Java 8. Этот метод очень полезен для выполнения заданного действия для каждой записи в карте до тех пор, пока все записи не будут обработаны или действие не вызовет исключение.

package com.journaldev.examples;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

public class HashMapForEachExample {

	public static void main(String[] args) {
		Map map = new HashMap<>();
		map.put("1", "1");
		map.put("2", null);
		map.put(null, "100");

		BiConsumer action = new MyBiConsumer();
		map.forEach(action);
		
		//пример лямбда-выражения
		System.out.println("\nHashMap forEach lambda example\n");
		map.forEach((k,v) -> {System.out.println("Key = "+k+", Value = "+v);});
	}

}

class MyBiConsumer implements BiConsumer {

	@Override
	public void accept(String t, String u) {
		System.out.println("Key = " + t);
		System.out.println("Processing on value = " + u);
	}

}

Результат выполнения приведенной выше программы с использованием метода forEach в HashMap;

Key = null
Processing on value = 100
Key = 1
Processing on value = 1
Key = 2
Processing on value = null

HashMap forEach lambda example

Key = null, Value = 100
Key = 1, Value = 1
Key = 2, Value = null

Java HashMap replaceAll

Метод replaceAll в HashMap может использоваться для замены значения каждой записи результатом вызова заданной функции для этой записи. Этот метод добавлен в Java 8, и мы можем использовать лямбда-выражения в качестве аргумента для этого метода.

package com.journaldev.examples;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;

public class HashMapReplaceAllExample {

	public static void main(String[] args) {
		Map map = new HashMap<>();
		map.put("1", "1");
		map.put("2", "2");
		map.put(null, "100");

		System.out.println("map before replaceAll = " + map);
		BiFunction function = new MyBiFunction();
		map.replaceAll(function);
		System.out.println("map after replaceAll = " + map);

		//replaceAll с использованием лямбда-выражений
		map.replaceAll((k, v) -> {
			if (k != null) return k + v;
			else return v;});
		System.out.println("map after replaceAll lambda expression = " + map);

	}

}

class MyBiFunction implements BiFunction {

	@Override
	public String apply(String t, String u) {
		if (t != null)
			return t + u;
		else
			return u;
	}

}

Результат выполнения приведенной выше программы с использованием метода replaceAll в HashMap;

map before replaceAll = {null=100, 1=1, 2=2}
map after replaceAll = {null=100, 1=11, 2=22}
map after replaceAll lambda example = {null=100, 1=111, 2=222}

Java HashMap computeIfAbsent

Метод computeIfAbsent в HashMap вычисляет значение только в том случае, если ключ отсутствует в карте. После вычисления значения оно помещается в карту, если оно не является пустым.

package com.journaldev.examples;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

public class HashMapComputeIfAbsent {

	public static void main(String[] args) {
		Map map = new HashMap<>();
		map.put("1", "10");
		map.put("2", "20");
		map.put(null, "100");

		Function function = new MyFunction();
		map.computeIfAbsent("3", function); //key not present
		map.computeIfAbsent("2", function); //key already present
		
		//лямбда-стиль
		map.computeIfAbsent("4", v -> {return v;});
		map.computeIfAbsent("5", v -> {return null;}); //null value won't get inserted
		System.out.println(map);
	}

}

class MyFunction implements Function {

	@Override
	public String apply(String t) {
		return t;
	}
	
}

Результат выполнения приведенной выше программы;

{null=100, 1=10, 2=20, 3=3, 4=4}

Java HashMap computeIfPresent

Метод computeIfPresent класса Java HashMap пересчитывает значение, если указанный ключ присутствует и значение не является нулевым. Если функция возвращает null, то отображение удаляется.

package com.journaldev.examples;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;

public class HashMapComputeIfPresentExample {

	public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		map.put("1", "10");
		map.put("2", "20");
		map.put(null, "100");
		map.put("10", null);

		System.out.println("map before computeIfPresent = " + map);
		BiFunction<String, String, String> function = new MyBiFunction1();
		for (String key : map.keySet()) {
			map.computeIfPresent(key, function);
		}
		
		System.out.println("map after computeIfPresent = " + map);
		map.computeIfPresent("1", (k,v) -> {return null;}); // mapping will be removed
		System.out.println("map after computeIfPresent = " + map);

	}

}

class MyBiFunction1 implements BiFunction<String, String, String> {

	@Override
	public String apply(String t, String u) {
		return t + u;
	}

}

Пример вывода, созданный с использованием метода computeIfPresent класса HashMap, следующий:

map before computeIfPresent = {null=100, 1=10, 2=20, 10=null}
map after computeIfPresent = {null=null100, 1=110, 2=220, 10=null}
map after computeIfPresent = {null=null100, 2=220, 10=null}

Java HashMap compute

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

package com.journaldev.examples;

import java.util.HashMap;
import java.util.Map;

public class HashMapComputeExample {

	public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		map.put("1", "1");
		map.put("2", "2");
		map.put(null, "10");
		map.put("10", null);

		System.out.println("map before compute = "+map);
		for (String key : map.keySet()) {
			map.compute(key, (k,v) -> {return k+v;});
		}
		map.compute("5", (k,v) -> {return k+v;}); //key not present, v = null
		System.out.println("map after compute = "+map);
	}

}

Пример вывода метода compute класса HashMap следующий:

map before compute = {null=10, 1=1, 2=2, 10=null}
map after compute = {null=null10, 1=11, 2=22, 5=5null, 10=10null}

Java HashMap merge

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

package com.journaldev.examples;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class HashMapMergeExample {

	public static void main(String[] args) {
		Map map = new HashMap<>();
		map.put("1", "1");
		map.put("2", "2");
		map.put(null, "10");
		map.put("10", null);

		for (Entry entry : map.entrySet()) {
			String key = entry.getKey();
			String value = entry.getValue();
			//merge генерирует исключение NullPointerException, если ключ или значение равны null
			if(key != null && value != null) 
			map.merge(entry.getKey(), entry.getValue(), 
					(k, v) -> {return k + v;});
		}
		System.out.println(map);
		
		map.merge("5", "5", (k, v) -> {return k + v;}); // key not present
		System.out.println(map);
		
		map.merge("1", "1", (k, v) -> {return null;}); // method return null, so remove
		System.out.println(map);

	}

}

Результат выполнения вышеуказанной программы:

{null=10, 1=11, 2=22, 10=null}
{null=10, 1=11, 2=22, 5=5, 10=null}
{null=10, 2=22, 5=5, 10=null}

Это все, что касается HashMap в Java, надеюсь, что ничего важного не упущено. Делитесь этим с другими, если вам понравилось. Ссылка: API Документация

Source:
https://www.digitalocean.com/community/tutorials/java-hashmap