Java HashMap – Java中的HashMap

时间:2020-02-23 14:34:28  来源:igfitidea点击:

Java HashMap是Java中最受欢迎的Collection类之一。
Java HashMap是基于哈希表的实现。
Java中的HashMap扩展了实现Map接口的AbstractMap类。

Java HashMap

关于Java中的HashMap的一些重点是:

  • Java HashMap允许空键和空值。

  • HashMap不是有序集合。
    您可以通过键集遍历HashMap条目,但是不能保证它们按其添加到HashMap的顺序。

  • HashMap几乎与Hashtable相似,但它是不同步的,并且允许空键和空值。

  • HashMap使用其内部类Node <K,V>来存储地图条目。

  • HashMap将条目存储到多个单独链接的列表中,这些列表称为存储桶或者存储箱。
    默认箱数为16,始终为2的幂。

  • HashMap在密钥上使用hashCode()和equals()方法进行获取和放置操作。
    因此,HashMap关键对象应提供这些方法的良好实现。
    这就是不可变类更适合于键的原因,例如String和Interger。

  • Java HashMap不是线程安全的,对于多线程环境,您应该使用ConcurrentHashMap类或者使用Collections.synchronizedMap()方法获取同步映射。

Java HashMap构造函数

Java HashMap提供了四个构造函数。

  • public HashMap():最常用的HashMap构造函数。
    此构造函数将创建一个空的HashMap,其默认初始容量为16,负载因子为0.75。

  • public HashMap(int initialCapacity):此HashMap构造函数用于指定初始容量和0.75负载系数。
    如果您知道要在HashMap中存储的映射数,则这对于避免重新散列很有用。

  • public HashMap(int initialCapacity,float loadFactor):此HashMap构造函数将创建一个具有指定初始容量和负载因子的空HashMap。
    如果您知道要在HashMap中存储的最大映射数,则可以使用此方法。
    在常见情况下,应避免这种情况,因为负载系数0.75在空间和时间成本之间提供了很好的权衡。

  • public HashMap(Map <?扩展K,?扩展V> m):创建一个具有与指定地图相同的映射并且负载因子为0.75的Map

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的重要方法。

  • public void clear():此HashMap方法将删除所有映射,并且HashMap将变为空。

  • public boolean containsKey(Object key):如果该键存在,则此方法返回" true",否则将返回" false"。

  • public boolean containsValue(Object value):如果值存在,则此HashMap方法返回true,否则返回false。

  • 公共Set <Map.Entry <K,V >> entrySet():此方法返回HashMap映射的Set视图。
    该集合由地图支持,因此对地图的更改会反映在集合中,反之亦然。

  • public V get(Object key):返回映射到指定键的值;如果没有该键的映射,则返回null。

  • public boolean isEmpty():如果不存在键-值映射关系,则返回true的实用程序方法。

  • public Set <K> keySet():返回此映射中包含的键的Set视图。
    该集合由地图支持,因此对地图的更改会反映在集合中,反之亦然。

  • public V put(K key,V value):在地图中将指定的值与指定的键相关联。
    如果该映射先前包含该键的映射,则将替换旧值。

  • public void putAll(Map <?扩展K,?扩展V> m):将所有映射从指定映射复制到此映射。
    这些映射将替换该映射对指定映射中当前存在的任何键的任何映射。

  • public V remove(Object key):如果存在,则从此映射中删除指定键的映射。

  • public int size():返回此映射中的键值映射数。

  • public Collection <V> values():返回此映射中包含的值的Collection视图。
    集合由地图支持,因此对地图的更改会反映在集合中,反之亦然。

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

  • public V computeIfAbsent(K key,Function <?super K,?extended V> mappingFunction):如果指定的键尚未与值关联(或者映射为null),则此方法尝试使用给定的映射来计算其值函数,除非Null,否则将其输入到HashMap中。

  • 公用V computeIfPresent(K键,BiFunction <?超级K,?映射值。

  • 公共V计算(K键,BiFunction <?super K,?super V ,?扩展V> remappingFunction):此HashMap方法尝试计算指定键及其当前映射值的映射。

  • public void forEach(BiConsumer <?super K,?super V>操作):此方法对映射中的每个条目执行给定的操作。

  • public V getOrDefault(Object key,V defaultValue):与get相同,不同之处在于,如果未找到指定键的映射关系,则返回defaultValue。

  • 公共V merge(K键,V值,BiFunction <?super V,?super V,?extended V> remappingFunction):如果指定的键尚未与值关联或者与null关联,则将其与给定的non关联-null值。
    否则,用给定的重映射函数的结果替换关联的值,如果结果为null,则将其删除。

  • public V putIfAbsent(K key,V value):如果指定的键尚未与值关联(或者映射为null),则将其与给定值关联并返回null,否则返回当前值。

  • public boolean remove(Object key,Object value):仅当当前映射到指定值时,才删除指定键的条目。

  • public boolean replace(K key,V oldValue,V newValue):仅当当前映射到指定值时,才替换指定键的条目。

  • public V replace(K键,V值):仅当指定键当前映射到某个值时,才替换该条目。

  • public void replaceAll(BiFunction <?super K,?super V,?extends V>函数):将每个条目的值替换为在该条目上调用给定函数的结果。

Java HashMap示例

这是用于HashMap常用方法的简单Java程序。

package com.theitroad.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中工作?

Java中的HashMap使用其内部类Node <K,V>来存储映射。
HashMap使用哈希算法,并在密钥上使用hashCode()和equals()方法进行获取和放置操作。

HashMap使用单链表来存储元素,这些元素称为箱或者桶。
当我们调用put方法时,key的hashCode用于确定将用于存储映射的存储桶。

标识存储桶后,将使用hashCode来检查是否已经存在具有相同hashCode的密钥。
如果存在具有相同hashCode的键,则对键使用equals()方法。
如果equals返回true,则值将被覆盖,否则对该单链接列表存储桶进行新映射。
如果没有具有相同hashCode的键,则将映射插入到存储桶中。

对于HashMap get操作,再次使用键hashCode来确定要查找值的存储桶。
标识存储桶后,使用hashCode和equals方法遍历条目以找出条目。
如果找到匹配项,则返回value,否则返回null。

涉及更多的事情,例如散列算法以获取密钥的存储桶,重新哈希映射等。
但是对于我们的工作,请记住,HashMap操作对Key起作用,并且hashCode的良好实现,并且equals方法是必需的,以避免不必要的行为。
下图显示了get和put操作的说明。

Java HashMap负载因子

加载因子用于确定何时重新哈希哈希映射和增加存储桶大小。
铲斗或者容量的默认值为16,负载系数为0.75。
通过将容量和负载因子相乘来计算用于重新哈希处理的阈值。
因此,默认阈值将为12。
因此,当HashMap具有超过12个映射时,它将被重新映射,并且bin的数量将增加到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不由map支持的方法,该怎么办。

package com.theitroad.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由map支持。

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

Java HashMap值

Java HashMap values方法返回Map中值的Collection视图。
此集合由HashMap支持,因此HashMap中的任何更改都将反映在值集合中,反之亦然。
下面的一个简单示例确认了HashMap值收集的这种行为。

package com.theitroad.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.theitroad.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

Java 8中引入的HashMap putIfAbsent方法的简单示例。

package com.theitroad.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.theitroad.examples;

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

public class HashMapForEachExample {

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

		BiConsumer<String, String> action = new MyBiConsumer();
		map.forEach(action);
		
		//lambda expression example
		System.out.println("\nHashMap forEach lambda example\n");
		map.forEach((k,v) -> {System.out.println("Key = "+k+", Value = "+v);});
	}

}

class MyBiConsumer implements BiConsumer<String, String> {

	@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.theitroad.examples;

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

public class HashMapReplaceAllExample {

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

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

		//replaceAll using lambda expressions
		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<String, String, String> {

	@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方法仅在键不存在于映射中时才计算该值。
计算完值后,如果不为空,则将其放入地图中。

package com.theitroad.examples;

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

public class HashMapComputeIfAbsent {

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

		Function<String, String> function = new MyFunction();
		map.computeIfAbsent("3", function); //key not present
		map.computeIfAbsent("2", function); //key already present
		
		//lambda way
		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<String, String> {

	@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.theitroad.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计算

如果您要根据函数的键和值在所有映射上应用函数,则应使用计算方法。
如果没有映射并且使用此方法,则计算功能的值将为null。

package com.theitroad.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计算示例的输出为;

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合并

如果指定的键不存在或者与null关联,则将其与给定的非null值关联。
否则,用给定的重映射函数的结果替换关联的值,如果结果为null,则将其删除。

package com.theitroad.examples;

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

public class HashMapMergeExample {

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

		for (Entry<String, String> entry : map.entrySet()) {
			String key = entry.getKey();
			String value = entry.getValue();
			//merge throws NullPointerException if key or value is 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}