# Java `Hashmap`教程

> 原文： [https://javabeginnerstutorial.com/core-java-tutorial/java-hashmap/](https://javabeginnerstutorial.com/core-java-tutorial/java-hashmap/)

Java `HashMap`是基于哈希表的`Map`实现。 这就是为什么面试官总是要求`HashMap`和`HashTable`之间存在差异的原因。 `HashMap`基本上等于`HashTable`，但以下两个区别除外。

1.  当`HashTable`同步时，`HashMap`不同步。
2.  `HashMap`允许为`null`，而`HashTable`不允许为`null`。

## `HashMap`的重要属性

| | |
| --- | --- |
| **`DEFAULT_INITIAL_CAPACITY`** | 默认初始容量（2 的 n 次幂）。 `HashMap`可以包含许多元素。 |
| **`MAXIMUM_CAPACITY`** | `HashMap`的最大容量（2 的 n 次幂）。 |
| **`loadFactor`** | 定义`HashMap`的阈值。 重新调整大小时将在`HashMap`中发生。 |
| **`DEFAULT_LOAD_FACTOR`** | 在`HashMap`的构造器中未定义任何负载因子时将使用。 |
| **`size`**  | `HashMap`包含的键/值对映射数。 |

## 创建`HashMap`

在创建`HashMap`时未定义任何参数时，将使用默认的初始容量（16）和默认的负载系数（0.75）。 该`HashMap`最多可以包含 16 个元素，并且在插入第 13 个元素时会调整`HashMap`的大小。 这是因为负载系数为 75% （.75），并且在添加第 13 个元素（`12 + 1`）时将超过此阈值。

您还可以提供初始容量和`loadFactor`。 但是初始容量不能超过最大容量（2 的 30 次幂），并且负载系数不能为零或负数。

## `HashMap`中元素的添加

为了添加一个元素，您需要提供 2 个东西：键和值。

**键**：将与指定值关联的键。 `null`是允许的。

**值**：与指定键关联的值。

首先，`HashMap`将为给定密钥生成一个哈希码，然后检查是否已经有与给定密钥相关联的值。 如果是，则它将返回已经关联的值。 否则，它将通过提供的键在`HashMap`中增加价值。

## 重点

1.  `HashMap`不会按`Map`中的元素顺序提供任何保证（均值顺序会随时间变化）。
2.  `HashMap`为**获得**和**设置**操作提供恒定时间性能（如果使用正确的哈希算法）。
3.  迭代收集所需的时间与`HashMap`的“**容量**”（它可以容纳的元素）和**大小**（它当前容纳的元素）成比例。
4.  如果迭代性能更为重要，那么建议不要将初始容量设置得太高而将负载系数设置得太低。 由于性能与*初始容量*和*负载系数*成正比。
    *   *容量*是哈希表中的存储桶数。
    *   初始容量（默认值为 16）只是创​​建哈希表时的容量。
    *   *负载因子（默认值 .75）*是衡量哈希表在自动增加其容量之前的填充程度的度量。
    *   当哈希表中的条目数超过负载因子与当前容量的乘积时，哈希表将被*重映射*（即内部数据结构将被重建）。
5.  使用“`Collections.synchronizedMap()`”方法使映射同步。
6.  由`HashMap`类返回的迭代器为“**故障快速**”。
7.  `HashMap`由数组（`Key`）和`LinkedList`（`Value`）支持。
8.  `HashMap`使用`hashcode`（使用密钥）来标识应在`HashMap`中放置或检索对象的确切位置。
9.  最后，`HashCode`返回后备数组中的确切位置（索引）。
10.  支持数组的大小固定。 因此，只要数组已满（此映射中的键数达到其阈值）。 将创建一个具有新容量的新数组，并将所有元素添加到该新数组中。
11.  在两种情况下（添加和检索对象）都将使用`HashCode`，而在任何情况下均可以使用或可以不使用`equals()`方法。
12.  `HashMap`中`Key`的最佳候选者是具有正确实现`Equals`和`Hashcode`方法的不可变类（示例：字符串类）。
13.  更好的哈希码和`equals`方法实现是`HashMap`的更好性能。
14.  这样，所有`String`和原始类型的包装类将是`HashMap`中键的理想选择。

# 什么是重新哈希

每个`HashMap`都有预定义的大小（初始容量），以及在需要时（超过阈值限制时）增加此大小（负载系数）的逻辑。

**示例**：

使用以下配置创建`HashMap`

初始容量：16（*默认初始容量*）

负载系数：.75（*默认负载系数*）

在给定的`HashMap`中添加第 13 个元素后，超过给定`HashMap`的阈值限制，系统将创建一个新的后备键集数组（此数组的大小将是前一个数组的两倍）。 系统将不得不再次计算确切的存储桶，应放置上一个存储桶中的元素，并将旧`HashMap`中的所有元素复制到新的`HashMap`中。 整个过程称为重新哈希，因为会再次为每个元素计算`Hashcode`。

因为超时的`HashMap`可能会被重新散布并且顺序可能会发生变化。

# `HashMap`的例子

在此示例中，您将学到以下几点

1.  如何迭代映射
2.  迭代映射的不同方法
3.  `HashCode`和`Equals`何时被调用。 （请特别注意`HashCode`的输出和`equals`方法调用）

```java
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class HashMapExample {

	public static void main(String[] args) {

		Map<JBT, Integer> m1 = new HashMap<JBT, Integer>();

		JBT t1 = new JBT(1, 2);
		JBT t2 = new JBT(1, 3);
		JBT t3 = new JBT(2, 1);

		m1.put(t1, 1);
		m1.put(t2, 2);
		m1.put(t3, 3);
		System.out.println("Addition Done");

		/*
		 * Below you can find 3 different ways to iterate a Map. Uncomment
		 * different section and see the different in Output. Pay attention to
		 * when Hashcode and Equals is called
		 */

/*		Set s = m1.entrySet();
		for (Iterator i = s.iterator(); i.hasNext();) {
			Map.Entry me = (Map.Entry) i.next();
			System.out.println(me.getKey() + " : " + me.getValue());
		}
*/
/*		for (Map.Entry<JBT, Integer> entry : m1.entrySet()) {
			System.out.println("Key : " + entry.getKey() + " Value : "
					+ entry.getValue());
		}
*/
		for (Object key : m1.keySet()) {
			System.out.println("Key : " + key.toString() + " Value : "
					+ m1.get(key));
		}

	}
}

class JBT {
	JBT(int i, int j) {
		this.i = i;
		this.j = j;
	}

	int i, j;

	@Override
	public int hashCode() {
		System.out.println("Inside HashCode Method");
		int k = i + j;
		return k;
	}

	@Override
	public boolean equals(Object obj) {

		System.out.println("Inside Equals Method");
		if (i == ((JBT) obj).i && j == ((JBT) obj).j)
			return true;
		else
			return false;
	}

	@Override
	public String toString() {
		return String.valueOf(i).concat(String.valueOf(j));
	}

} 
```

