Java HashMap – Java 中的 HashMap

Java HashMap 是 Java 中最受歡迎的集合類之一。Java HashMap 是基於哈希表的實現。HashMap 在 Java 中擴展了 AbstractMap 類,該類實現了 Map 接口。

Java HashMap

有關 Java 中 HashMap 的一些重要信息是:

  1. Java HashMap 允許 null 鍵和 null 值。
  2. HashMap 不是有序集合。您可以通過鍵集遍歷 HashMap 条目,但不能保證它們按照添加到 HashMap 的順序排列。
  3. HashMap 幾乎與 Hashtable 相似,只是它是非同步的,並允許 null 鍵和值。
  4. HashMap 使用它的內部類 Node 來存儲映射條目。
  5. HashMap 將條目存儲到多個單鏈表中,稱為桶或箱。默認的桶數量是 16,並且始終是 2 的冪次方。
  6. HashMap 在 get 和 put 操作中使用鍵的 hashCode() 和 equals() 方法。因此,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。在常见情况下,应避免使用此构造函数,因为负载因子0.75在空间和时间成本之间提供了良好的平衡。
  4. public HashMap(Map<? extends K, ? extends V> m): 创建一个具有与指定映射相同的映射的 Map,并具有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 方法

讓我們來看看 Java 中 HashMap 的重要方法。

  1. public void clear():此 HashMap 方法將刪除所有映射,並使 HashMap 變為空。
  2. public boolean containsKey(Object key):如果鍵存在則返回 true,否則返回 false。
  3. public boolean containsValue(Object value):如果值存在則返回 true,否則返回 false。
  4. public Set<Map.Entry<K,V>> entrySet():此方法返回 HashMap 映射的 Set 觀點。該集合由地圖支持,因此對地圖的更改會反映在集合中,反之亦然。
  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 m):將指定地圖中的所有映射複製到此地圖。這些映射將取代此地圖對於指定地圖中當前所有鍵的任何映射。
  10. public V remove(Object key):如果存在,從此地圖中刪除指定鍵的映射。
  11. public int size():返回此地圖中的鍵值映射數量。
  12. public Collection values():返回此地圖中包含的值的 Collection 觀點。該集合由地圖支持,因此地圖的更改會反映在集合中,反之亦然。

HashMap 中在 Java 8 中引入了許多新方法。

  1. public V computeIfAbsent(K key, Function mappingFunction):如果指定鍵尚未與值關聯(或映射為 null),則此方法嘗試使用給定的映射函數計算其值並將其輸入 HashMap,除非為 Null。
  2. public V computeIfPresent(K key, BiFunction remappingFunction):如果指定鍵的值存在且非 null,則嘗試根據鍵及其當前映射的值計算新映射。
  3. public V compute(K key, BiFunction 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關聯,則將其與給定的非null值關聯起來。否則,將與給定重映射函數的結果替換關聯的值,如果結果為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 示例

这是一个用于演示HashMap常用方法的简单Java程序。

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 来存储映射。HashMap 基于哈希算法工作,并使用键的 hashCode() 和 equals() 方法进行获取和放置操作。HashMap 使用单向链表来存储元素,这些称为 bins 或 buckets。当我们调用 put 方法时,使用键的 hashCode 来确定将用于存储映射的 bucket。一旦确定了 bucket,就使用 hashCode 来检查是否已经存在具有相同 hashCode 的键。如果已经存在具有相同 hashCode 的键,则在键上使用 equals() 方法。如果 equals 返回 true,则值被覆盖,否则会将新的映射添加到这个单向链表 bucket 中。如果没有具有相同 hashCode 的键,则将映射插入到 bucket 中。对于 HashMap 的 get 操作,再次使用键的 hashCode 来确定要查找值的 bucket。确定了 bucket 后,通过使用 hashCode 和 equals 方法来遍历条目以找出 Entry。如果找到匹配项,则返回值,否则返回 null。还涉及许多其他事项,例如获取键的 bucket 所需的哈希算法、重新映射映射等。但对于我们的工作来说,只需记住 HashMap 操作是在键上进行的,并且需要良好实现的 hashCode 和 equals 方法以避免不必要的行为。下面的图像显示了获取和放置操作的解释。 推荐阅读: Java 中 hashCode 和 equals 方法的重要性

Java HashMap 加载因子

加载因子用于确定何时对 HashMap 进行重新哈希以及桶的大小将会增加。桶或容量的默认值为 16,加载因子为 0.75。重新哈希的阈值是通过将容量和加载因子相乘来计算的。因此,默认阈值将为 12。因此,当 HashMap 具有超过 12 个映射时,它将被重新哈希,桶的数量将增加到下一个2的幂,即32。请注意,HashMap 的容量始终是2的幂。默认加载因子为 0.75 在空间和时间复杂性之间提供了良好的权衡。但根据您的需求,可以将其设置为不同的值。如果要节省空间,可以将其增加到 0.80 或 0.90,但此时获取/放置操作将花费更多时间。

Java HashMap keySet

Java HashMap 的 keySet 方法返回 HashMap 中键的 Set 视图。此 Set 视图由 HashMap 支持,HashMap 中的任何更改都会反映在 Set 中,反之亦然。下面是一个演示 HashMap keySet 示例的简单程序,以及如果需要一个不由映射支持的 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 值方法返回映射中值的集合视图。此集合由 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 方法返回映射的 Set 视图。此 entrySet 由 HashMap 支持,因此地图中的任何更改都会反映在条目集中,反之亦然。请查看下面的 HashMap entrySet 示例程序。

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

HashMap forEach方法是在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);
		
		// lambda表达式示例
		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);
	}

}

上述HashMap forEach示例程序的输出是:

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

HashMap replaceAll方法可用于用调用该条目上给定函数的结果替换每个条目的值。此方法在Java 8中添加,我们可以使用lambda表达式作为此方法的参数。

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);

		// 使用lambda表达式的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;
	}

}

上述HashMap replaceAll程序的输出是:

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

HashMap computeIfAbsent方法仅在地图中不存在键时才计算值。计算值后,如果值不为null,则将其放入地图中。

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
		
		// lambda方式
		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

Java HashMap computeIfPresent 方法如果指定的鍵存在且值不為 null,則重新計算該值。如果函數返回 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;
	}

}

HashMap computeIfPresent 示例產生的輸出是:

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 方法。如果沒有映射且使用了此方法,則對於 compute 函數,值將為 null。

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);
	}

}

HashMap compute 示例的輸出是:

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 值關聯。否則,將關聯值替換為給定重新映射函數的結果,如果結果為 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();
			// 如果鍵或值為 null,merge 會拋出 NullPointerException
			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}

那就是 Java 中的 HashMap 全部了,希望没有漏掉什么重要的内容。如果喜欢的话,也请与他人分享。参考:API 文档

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