Java HashMap 是 Java 中最流行的 Collection 类之一。Java HashMap 是基于哈希表的实现。Java 中的 HashMap 扩展了实现 Map 接口的 AbstractMap 类。
1. HashMap 主要特点
- HashMap 允许空键和空值。
- HashMap 不是有序集合。您可以通过键集迭代 HashMap 条目,但不能保证它们按照添加到 HashMap 的顺序排列。
- HashMap 与 Hashtable 非常相似,只是它不同步并且允许键和值为空。
- HashMap 使用其内部类 Node<K,V> 来存储地图条目。
- HashMap 将条目存储在多个单链表中,称为桶或箱。默认的箱数为 16,并且始终是 2 的幂。
- HashMap 使用 hashCode() 和 equals() 方法对键进行获取和放置操作。因此 HashMap 键对象应该提供这些方法的良好实现。这就是不可变类更适合键例如 String 和 Interger的原因。
- 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 方法以避免不必要的行为。
下图显示了 get 和 put 操作的解释。
4. HashMap 负载因子loadFactor
负载因子用于确定 HashMap 何时重新散列以及桶大小是否增加。桶或容量的默认值为 16,负载因子为 0.75。重新散列的阈值通过将容量和负载因子相乘计算得出。因此默认阈值为 12。
当 HashMap 具有超过 12 个映射时,它将重新散列并且桶数将增加到下一个 2 的幂,即 32。请注意,HashMap 容量始终是 2 的幂。默认负载因子 0.75 在空间和时间复杂度之间提供了良好的权衡。但您可以根据需要将其设置为不同的值。
如果您想节省空间,则可以将其值增加到 0.80 或 0.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 文档