Java HashMap – Java中的HashMap

Java HashMap 是 Java 中最受欢迎的 Collection 类之一。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<K,V> 来存储映射条目。
  5. HashMap 将条目存储到多个称为桶或箱子的单向链表中。默认桶的数量是 16,而且始终是 2 的幂。
  6. HashMap 在获取和放置操作中使用键的 hashCode() 和 equals() 方法。因此,HashMap 键对象应提供这些方法的良好实现。这就是为什么不可变类更适合用作键的原因,例如 String 和 Interger。
  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 方法

让我们来看看 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 视图。该集合由映射支持,因此对映射的更改会反映在集合中,反之亦然。

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

  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 示例

这里是一个简单的 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使用单链表来存储元素,这些称为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操作基于Key,需要良好实现的hashCode和equals方法以避免不必要的行为。下面的图片显示了get和put操作的说明。 推荐阅读: 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 的 values 方法返回 Map 中值的 Collection 视图。此 Collection 受 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 支持,因此对映射的任何更改都会反映在 entry set 中,反之亦然。请查看下面的 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,则移除映射。

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