Java HashMap原理

在这里插入图片描述
Java HashMap 是 Java 中最流行的 Collection 类之一。Java HashMap 是基于哈希表的实现。Java 中的 HashMap 扩展了实现 Map 接口的 AbstractMap 类。

1. HashMap 主要特点

  1. HashMap 允许空键和空值
  2. HashMap 不是有序集合。您可以通过键集迭代 HashMap 条目,但不能保证它们按照添加到 HashMap 的顺序排列。
  3. HashMap 与 Hashtable 非常相似,只是它不同步并且允许键和值为空。
  4. HashMap 使用其内部类 Node<K,V> 来存储地图条目。
  5. HashMap 将条目存储在多个单链表中,称为桶或箱。默认的箱数为 16,并且始终是 2 的幂
  6. HashMap 使用 hashCode() 和 equals() 方法对键进行获取和放置操作。因此 HashMap 键对象应该提供这些方法的良好实现。这就是不可变类更适合键例如 String 和 Interger的原因。
  7. HashMap 不是线程安全的,对于多线程环境,您应该使用 ConcurrentHashMap 类或使用Collections.synchronizedMap()方法获取同步映射。

2. HashMap 构造函数

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

2.1 public HashMap()

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

2.2 public HashMap(int initialCapacity)

此 HashMap 构造函数用于指定初始容量0.75 负载因子。如果您知道要存储在 HashMap 中的映射数,这将有助于避免重新散列。

2.3 public HashMap(int initialCapacity, float loadFactor)

此 HashMap 构造函数将创建一个具有指定初始容量负载因子的空 HashMap。如果您知道 HashMap 中要存储的最大映射数,则可以使用此构造函数。但在通常情况下,您不必用该构造函数,因为负载因子 0.75 在空间和时间成本之间提供了良好的权衡

2.4 public HashMap(Map<? extends K, ? extends V> m)

创建一个具有与指定映射相同的映射且负载因子为 0.75 的 Map。
在这里插入图片描述

3. HashMap 在 工作原理

在这里插入图片描述
Java 中的 HashMap 使用其内部类 Node<K,V> 来存储映射。HashMap 采用哈希算法,并使用 key 上的 hashCode()equals() 方法执行获取和放置操作。

HashMap 使用单链表来存储元素,这些元素称为容器存储桶。当我们调用 put 方法时,key 的 hashCode 用于确定将用于存储映射的存储桶。一旦确定了存储桶,就会使用 hashCode 检查是否已经存在具有相同 hashCode 的 key。

如果存在具有相同 hashCode 的现有 key,则对 key 使用 equals() 方法。如果 equals 返回 true,则覆盖值,否则将对此单链表存储桶进行新的映射。

如果没有具有相同 hashCode 的 key,则将映射插入存储桶。对于 HashMap 获取操作,再次使用 key hashCode 来确定要查找值的存储桶。确定存储桶后,将遍历条目以使用 hashCode 和 equals 方法找出条目。如果找到匹配项,则返回值,否则返回 null。还有很多事情需要处理,例如获取密钥存储桶的哈希算法、映射的重新哈希等。但对于我们的工作,只需记住 HashMap 操作在 Key 上工作,并且需要良好地实现 hashCode 和 equals 方法以避免不必要的行为。

下图显示了 getput 操作的解释。
在这里插入图片描述

4. HashMap 负载因子loadFactor

在这里插入图片描述

负载因子用于确定 HashMap 何时重新散列以及桶大小是否增加。桶或容量的默认值为 16,负载因子为 0.75。重新散列的阈值通过将容量和负载因子相乘计算得出。因此默认阈值为 12
在这里插入图片描述

当 HashMap 具有超过 12 个映射时,它将重新散列并且桶数将增加到下一个 2 的幂,即 32。请注意,HashMap 容量始终是 2 的幂。默认负载因子 0.75 在空间和时间复杂度之间提供了良好的权衡。但您可以根据需要将其设置为不同的值。
在这里插入图片描述

如果您想节省空间,则可以将其值增加到 0.800.90,但获取/放入操作将花费更多时间

5. HashMap 核心常用方法

HashMap的get和put过于普通,这里就不详细介绍,本节点重点介绍HashMap的核心的常用方法有keySet 、values、entrySet、putIfAbsent、forEach、replaceAll、computeIfAbsent、compute和merge。

5.1 keySet 键集

HashMap keySet 方法返回 HashMap 中键的 Set 视图。此 Set 视图由 HashMap 支持,并且 HashMap 中的任何更改都会反映在 Set 中,反之亦然。下面是一个简单的程序,演示了 HashMap keySet 示例以及如果您想要一个不受 map 支持的 keySet,该怎么做。

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]

5.2 values 值的集合

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

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}

5.3 entrySet 条目集合

HashMap entrySet 方法返回映射的 Set 视图。此 entrySet 由 HashMap 支持,因此映射中的任何更改都会反映在条目集中,反之亦然。请查看下面的 HashMap entrySet 示例程序。

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]

5.4 putIfAbsent

作用:先判断指定的键(key)是否存在,不存在则将键/值对插入到 HashMap 中。
返回值:如果所指定的 key 已经在 HashMap 中存在,返回和这个 key 值对应的 value, 如果所指定的 key 不在 HashMap 中存在,则返回 null。
注意:如果指定 key 之前已经和一个 null 值相关联了 ,则该方法也返回 null。

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

5.5 forEach

HashMap 的 forEach 方法是 Java 8 中引入的。这是一个非常有用的方法,可以对映射中的每个条目进行遍历,执行给定的操作,直到所有条目都被处理或该操作引发异常。

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

5.6 replaceAll 替换所有

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

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}

5.7 computeIfAbsent

HashMap computeIfAbsent 方法仅当 key 不存在于映射中时才计算值。计算值后,如果值不为空,则将其放入映射中。

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}

5.8 computeIfPresent

如果指定的键存在且值不为空,则 Java HashMap computeIfPresent 方法将重新计算值。如果函数返回 null,则删除映射。

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}

5.9 compute

如果要对所有基于其键和值的映射 “应用函数”,则应使用计算方法。如果没有映射并且使用此方法,则计算函数的值将为空。

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}

5.10 merge 合并

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

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}

6. 总结

以上就是 Java 中 HashMap 的全部内容,希望没有遗漏任何重要内容。如果您喜欢,也可以与其他人分享。参考:API 文档

  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙殿殿主

你的打赏是我精心创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值