В этом блоге в основном объясняется, как использовать четыре класса реализации интерфейса Map: HashMap, Hashtable, LinkedHashMap и TreeMap, а также различия между ними.
Примечание. Версия JDK, используемая в коде в этой статье, — 1.8.0_191.
Стоит отметить, что интерфейс Map является самостоятельным интерфейсом и не наследует интерфейс Collection (вот ключевой момент, часто задаваемый в интервью):
public interface Map<K,V> {
......
}
1. Использование HashMap
HashMap — это наиболее часто используемый класс реализации интерфейса Map, в котором хранятся пары ключ-значение.HashMap не гарантирует порядок элементов, но гарантирует, что ключ должен быть уникальным.
Объявление кода класса HashMap выглядит так:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
......
}
1.1 Добавление элементов
Есть три способа добавить элементы с помощью HashMap:
- put
- putIfAbsent
- putAll
Сначала посмотрите на использование метода put():
HashMap<String, String> platformMap = new HashMap<>();
// 添加元素
System.out.println(platformMap.put("cnblogs.com", "博客园"));
System.out.println(platformMap.put("juejin.im", "掘金"));
System.out.println(platformMap.put("map.weixin.qq.com", "微信公众号"));
System.out.println(platformMap.put("zwwhnly.com", "个人博客"));
// 添加重复的Key,没有添加成功,但是会更新Key对应的Value值
// 不过代码不会报错,而是返回已经存在Key对应的Value
System.out.println(platformMap.put("zwwhnly.com", "个人博客"));
Результат выполнения приведенного выше кода:
null
null
null
null
личный блог
Отладка кода также обнаружит, что в platformMap всего 4 элемента, и порядок элементов отличается от порядка их добавления:
Стоит отметить, что последняя строка кодаplatformMap.put("zwwhnly.com", "个人博客")Возвращаемое значение — «личный блог», то есть существующий ключ: zwwhnly.com, соответствующее значение значения.
Просто измените этот код на:
System.out.println(platformMap.put("zwwhnly.com", "个人博客2"));
Запустите код еще раз и обнаружите, что вывод не изменился, а в platformMap все еще есть 4 элемента, но содержимое элемента platformMap изменилось:
Если вы не хотите, чтобы значение Value было перезаписано при наличии ключа, вы можете изменить код следующим образом:
System.out.println(platformMap.putIfAbsent("zwwhnly.com", "个人博客2"));
Кроме того, HashMap также предоставляет метод putAll() для добавления элементов в пакетах.Метод использования следующий:
HashMap<String, String> platformMap = new HashMap<>();
HashMap<String, String> majorPlatfromMap = new HashMap<>();
// 添加元素
majorPlatfromMap.put("cnblogs.com", "博客园");
majorPlatfromMap.put("juejin.im", "掘金");
HashMap<String, String> otherPlatformMap = new HashMap<>();
otherPlatformMap.put("map.weixin.qq.com", "微信公众号");
otherPlatformMap.put("zwwhnly.com", "个人博客");
otherPlatformMap.put("cnblogs.com", "博客园2");
platformMap.putAll(majorPlatfromMap);
platformMap.putAll(otherPlatformMap);
Стоит отметить, что, поскольку у majorPlatfromMap и otherPlatformMap один и тот же ключ: cnblogs.com, окончательное значение ключа в platformMap — «cnblogs.com»: «Blog Park 2», как показано на следующем рисунке:
1.2 Получить элементы
Есть два способа получить элементы с помощью HashMap:
- get()
- getOrDefault()
Сначала посмотрите на использование метода get():
System.out.println(platformMap.get("cnblogs.com"));
System.out.println(platformMap.get("csdn.com"));
Выходной результат:
Блог Парк
null
Когда ключ не существует, если вам нужно установить значение по умолчанию, вы можете использовать getOrDefault():
System.out.println(platformMap.getOrDefault("csdn.com", "CSDN"));
Вывод приведенного выше кода: CSDN.
1.3 Получить количество элементов набора
Метод использования для получения количества элементов HashMap выглядит следующим образом:
System.out.println("platformMap的元素个数为:" + platformMap.size());
1.4 Удаление элементов
Есть 2 перегрузки для удаления элементов с помощью HashMap:
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
@Override
public boolean remove(Object key, Object value) {
return removeNode(hash(key), key, value, true, true) != null;
}
Метод использования следующий:
System.out.println(platformMap.remove("zwwhnly.com"));
System.out.println(platformMap.remove("zwwhnly.com"));
System.out.println(platformMap.remove("map.weixin.qq.com", "微信公众号"));
System.out.println(platformMap.remove("juejin.im", "博客园"));
Вывод приведенного выше кода:
личный блог
null
true
false
1.5 Изменение элементов
Изменение элементов с помощью HashMap имеет следующие 2 перегрузки:
@Override
public boolean replace(K key, V oldValue, V newValue) {
Node<K,V> e; V v;
if ((e = getNode(hash(key), key)) != null &&
((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
e.value = newValue;
afterNodeAccess(e);
return true;
}
return false;
}
@Override
public V replace(K key, V value) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) != null) {
V oldValue = e.value;
e.value = value;
afterNodeAccess(e);
return oldValue;
}
return null;
}
Метод использования следующий:
System.out.println(platformMap.replace("cnblogs.com", "博客园:https://www.cnblogs.com/zwwhnly/"));
System.out.println(platformMap.replace("juejin.im", "掘金", "掘金:https://juejin.cn/user/3245414056985831"));
Вывод приведенного выше кода:
Блог Парк
true
1.6 Определить, пуста ли коллекция
Метод использования для определения того, является ли HashMap пустым, выглядит следующим образом:
System.out.println("isEmpty:" + platformMap.isEmpty());
1.7 Элементы обхода (часто задают на собеседованиях)
Существует четыре основных способа обхода элементов HashMap:
- Используйте keySet, чтобы получить все ключи, затем перейдите
- Используйте Map.entrySet, чтобы получить все элементы, затем используйте итератор для обхода
- Используйте Map.entrySet, чтобы получить все элементы, затем используйте цикл foreach для прохождения
- Используйте значения напрямую, чтобы получить все значения, этот метод не может пройти через ключ
Среди них методы 2 и 3 используют два метода обхода коллекции Set, т.к.platformMap.entrySet()Возвращаемый тип — это коллекция Set, а тип элемента в ней —Map.Entry<K,V>:
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
Метод использования следующий:
System.out.println("方式1:使用keySet遍历");
for (String key : platformMap.keySet()) {
System.out.println("Key:" + key + ",Value:" + platformMap.get(key));
}
System.out.println();
System.out.println("方式2:通过Map.entrySet使用iterator遍历");
Iterator<Map.Entry<String, String>> iterator = platformMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println("Key:" + entry.getKey() + ",Value:" + entry.getValue());
}
System.out.println();
System.out.println("方式3:通过Map.entrySet使用iterator遍历");
for (Map.Entry<String, String> entry : platformMap.entrySet()) {
System.out.println("Key:" + entry.getKey() + ",Value:" + entry.getValue());
}
System.out.println();
System.out.println("方式4:使用values遍历,使用这种方式无法遍历Key");
for (String value : platformMap.values()) {
System.out.println(value);
}
1.8 Очистить коллекцию
Метод использования для очистки всех элементов в HashMap выглядит следующим образом:
platformMap.clear();
1.9 Полный пример кода
Для пунктов, объясненных выше, полный код выглядит следующим образом:
package collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class MapTest {
public static void main(String[] args) {
HashMap<String, String> platformMap = new HashMap<>();
HashMap<String, String> majorPlatfromMap = new HashMap<>();
// 添加元素
majorPlatfromMap.put("cnblogs.com", "博客园");
majorPlatfromMap.put("juejin.im", "掘金");
HashMap<String, String> otherPlatformMap = new HashMap<>();
otherPlatformMap.put("map.weixin.qq.com", "微信公众号");
otherPlatformMap.put("zwwhnly.com", "个人博客");
platformMap.putAll(majorPlatfromMap);
platformMap.putAll(otherPlatformMap);
System.out.println(platformMap.get("cnblogs.com"));
System.out.println(platformMap.get("csdn.com"));
System.out.println(platformMap.getOrDefault("csdn.com", "CSDN"));
System.out.println("platformMap的元素个数为:" + platformMap.size());
System.out.println(platformMap.remove("zwwhnly.com"));
System.out.println(platformMap.remove("zwwhnly.com"));
System.out.println(platformMap.remove("map.weixin.qq.com", "微信公众号"));
System.out.println(platformMap.remove("juejin.im", "博客园"));
System.out.println(platformMap.replace("cnblogs.com", "博客园:https://www.cnblogs.com/zwwhnly/"));
System.out.println(platformMap.replace("juejin.im", "掘金", "掘金:https://juejin.cn/user/3245414056985831"));
System.out.println("isEmpty:" + platformMap.isEmpty());
System.out.println("方式1:使用keySet遍历");
for (String key : platformMap.keySet()) {
System.out.println("Key:" + key + ",Value:" + platformMap.get(key));
}
System.out.println();
System.out.println("方式2:通过Map.entrySet使用iterator遍历");
Iterator<Map.Entry<String, String>> iterator = platformMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println("Key:" + entry.getKey() + ",Value:" + entry.getValue());
}
System.out.println();
System.out.println("方式3:通过Map.entrySet使用iterator遍历");
for (Map.Entry<String, String> entry : platformMap.entrySet()) {
System.out.println("Key:" + entry.getKey() + ",Value:" + entry.getValue());
}
System.out.println();
System.out.println("方式4:使用values遍历,使用这种方式无法遍历Key");
for (String value : platformMap.values()) {
System.out.println(value);
}
platformMap.clear();
System.out.println("isEmpty:" + platformMap.isEmpty());
}
}
Результат:
Блог Парк
null
CSDN
Количество элементов platformMap: 4
личный блог
null
true
false
Блог Парк
true
isEmpty:false
Способ 1: обход с помощью набора ключей
Ключ: cnblogs.com, Значение: Blog Park:www.cnblogs.com/zwwhnly/
Ключ: juejin.im, Значение: Самородки:Талант /user/324541…
Способ 2: обход Map.entrySet с использованием итератора
Ключ: cnblogs.com, Значение: Blog Park:www.cnblogs.com/zwwhnly/
Ключ: juejin.im, Значение: Самородки:Талант /user/324541…
Способ 3: обход Map.entrySet с использованием итератора
Ключ: cnblogs.com, Значение: Blog Park:www.cnblogs.com/zwwhnly/
Ключ: juejin.im, Значение: Самородки:Талант /user/324541…
Метод 4: Используйте значения для обхода, вы не можете пройти по ключу таким образом
Блог Сад:www.cnblogs.com/zwwhnly/
Самородки:Талант /user/324541…
isEmpty:true
2. Использование хеш-таблиц
Hashtable также является классом реализации интерфейса Map, стоит отметить, что все его методы синхронизированы, то есть потокобезопасны.
public synchronized int size() {
return count;
}
public synchronized boolean isEmpty() {
return count == 0;
}
Объявление кода класса HashTable выглядит так:
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
{
......
}
Также из приведенного выше кода видно, что базовым классом Hashtable является Dictionary, а базовым классом HashMap является AbstractMap (вот ключевой момент, часто задаваемый в интервью).
Использование класса HashTable в основном такое же, как и HashMap, просто измените код в следующем объявлении:
Hashtable<String, String> platformMap = new Hashtable<>();
Hashtable<String, String> majorPlatfromMap = new Hashtable<>();
Hashtable<String, String> otherPlatformMap = new Hashtable<>();
3. Использование LinkedHashMap
LinkedHashMap также является классом реализации интерфейса Map.По сравнению с HashMap, он использует связанный список, поэтому порядок вставки элементов может быть гарантирован, то есть FIFO (First Input First Output).
Объявление кода для класса LinkedHashMap выглядит следующим образом:
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
......
}
Как видно из приведенного выше кода, класс LinkedHashMap наследует класс HashMap.
Использование класса LinkedHashMap в основном такое же, как и у HashMap, просто измените код в объявлении:
LinkedHashMap<String, String> platformMap = new LinkedHashMap<>();
LinkedHashMap<String, String> majorPlatfromMap = new LinkedHashMap<>();
LinkedHashMap<String, String> otherPlatformMap = new LinkedHashMap<>();
4. Использование TreeMap
TreeMap также является классом реализации интерфейса Map.Стоит отметить, что элементы в TreeMap упорядочены, а правило сортировки по умолчанию — сортировка по возрастанию в соответствии с лексикографическим порядком ключей.
Объявление кода для класса TreeMap выглядит следующим образом:
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
......
}
Использование класса TreeMap в основном такое же, как и у HashMap, просто измените код в следующем объявлении:
TreeMap<String, String> platformMap = new TreeMap<>();
TreeMap<String, String> majorPlatfromMap = new TreeMap<>();
TreeMap<String, String> otherPlatformMap = new TreeMap<>();
5. Разница между HashMap, Hashtable, LinkedHashMap и TreeMap (ответьте на часто задаваемые вопросы)
5.1 Сходства
1) HashMap, Hashtable, LinkedHashMap, TreeMap реализуют интерфейс карты.
2) Все четыре обеспечивают уникальность Ключа, то есть повторение Ключа не допускается
5.2 Различия
5.2.1 Сортировка
HashMap не гарантирует порядок элементов
Hashtable не гарантирует порядок элементов
LinkHashMap гарантирует, что FIFO сортируется в порядке вставки
TreeMap гарантирует порядок элементов и поддерживает настраиваемые правила сортировки.
В воздухе нет доказательств, просто посмотрите на эффект от кода:
HashMap<String, String> hashMap = new HashMap<>();
Hashtable<String, String> hashtable = new Hashtable<>();
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
TreeMap<String, String> treeMap = new TreeMap<>();
String[] letterArray = new String[]{"B", "A", "D", "C", "E"};
for (String letter : letterArray) {
hashMap.put(letter, letter);
hashtable.put(letter, letter);
linkedHashMap.put(letter, letter);
treeMap.put(letter, letter);
}
System.out.println("HashMap(我不保证顺序):" + hashMap);
System.out.println("Hashtable(我不保证顺序):" + hashtable);
System.out.println("LinkedHashMap(我保证元素插入时的顺序):" + linkedHashMap);
System.out.println("TreeMap(我按排序规则保证元素的顺序):" + treeMap);
Вывод приведенного выше кода:
HashMap (порядок не гарантирую): {A=A, B=B, C=C, D=D, E=E}
Хеш-таблица (порядок не гарантирую): {A=A, E=E, D=D, C=C, B=B}
LinkedHashMap (гарантирую порядок вставки элементов): {B=B, A=A, D=D, C=C, E=E}
TreeMap (я гарантирую порядок элементов путем сопоставления): {A=A, B=B, C=C, D=D, E=E}
5.2.2 нулевое значение
HashMap, LinkedHashMap позволяет добавлять нулевые значения (как Key, так и Value), поэтому допустим следующий код:
HashMap<String, String> hashMap = new HashMap<>();
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
hashMap.put(null, null);
linkedHashMap.put(null, null);
TreeMap не позволяет Key иметь нулевые значения, но позволяет Value иметь нулевые значения, поэтому следующий код допустим:
TreeMap<String, String> treeMap = new TreeMap<>();
treeMap.put("cnblogs.com", null);
ноtreeMap.put(null, null);вызоветjava.lang.NullPointerExceptionаномальный:
Hashtable не позволяет добавлять нулевые значения (ни Key, ни Value), он будет выбрасывать при добавлении нулевых значенийjava.lang.NullPointerExceptionаномальный.
Hashtable<String, String> hashtable = new Hashtable<>();
hashtable.put("cnblogs.com", null);
hashtable.put(null, null);
Запустив приведенный выше код, сообщение об ошибке выглядит следующим образом:
5.2.3 Безопасность потоков
HashMap, LinkedHashMap, TreeMap не являются потокобезопасными.
Hashtable является потокобезопасным, что является его преимуществом, а также теоретически приводит к тому, что Hashtable не так эффективен, как HashMap.
Поэтому, если нет требований к потокобезопасности, рекомендуется использовать HashMap.
5.2.4 Наследование
Родительским классом Hashtable является Dictionary.
Родительским классом HashMap является AbstractMap.
Родительским классом LinkedHashMap является HashMap, а родительским классом HashMap является AbstractMap, поэтому LinkedHashMap также наследует AbstractMap.
Родительским классом TreeMap является AbstractMap.
6. Два метода сортировки TreeMap (часто задаваемые вопросы в интервью)
Правило сортировки по умолчанию для TreeMap — сортировка по возрастанию лексикографического порядка ключей.
Давайте сначала посмотрим на пример TreeMap, хранящий тип String:
TreeMap<String, String> treeMap = new TreeMap<>();
String[] letterArray = new String[]{"B", "A", "D", "C", "E"};
for (String letter : letterArray) {
treeMap.put(letter, letter);
}
for (String key : treeMap.keySet()) {
System.out.println("key:" + key + ",Value:" + treeMap.get(key));
}
Выходной результат:
key:A,Value:A
key:B,Value:B
key:C,Value:C
key:D,Value:D
key:E,Value:E
Тогда, если тип элемента, помещенный в TreeMap, является нашим пользовательским ссылочным типом, какова его сортировка?
Имея в виду этот вопрос, мы создаем новый класс Student следующим образом:
package collection;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Затем добавьте следующий код подтверждения:
TreeMap<Student, Student> studentTreeMap = new TreeMap<>();
Student student1 = new Student("zhangsan", 20);
Student student2 = new Student("lisi", 22);
Student student3 = new Student("wangwu", 24);
Student student4 = new Student("zhaoliu", 26);
Student student5 = new Student("zhangsan", 22);
studentTreeMap.put(student1, student1);
studentTreeMap.put(student2, student2);
studentTreeMap.put(student3, student3);
studentTreeMap.put(student4, student4);
studentTreeMap.put(student5, student5);
for (Student student : studentTreeMap.keySet()) {
System.out.println("name:" + student.getName() + ",age:" + student.getAge());
}
Я с радостью запустил код и хотел увидеть эффект, но обнаружил следующую ошибку:
Почему это так?
Это потому, что мы не определяем никаких правил сортировки для класса Student.TreeMap сказал, что я не знаю, как сортировать, так что давайте сгенерируем исключение, ха-ха.
Как это решить? Есть два способа:
- естественный порядок
- Компараторная сортировка
6.1 Естественный порядок
Способ реализации естественной сортировки состоит в том, чтобы позволить классу Student реализовать интерфейс Comparable и переопределить метод compareTo интерфейса, который определяет правила сортировки.
package collection;
public class Student implements Comparable<Student> {
// 省略其它代码
@Override
public int compareTo(Student o) {
return 0;
}
}
Выше показан метод compareTo(), сгенерированный по умолчанию с помощью IDEA.
Этот метод выполняется, когда элемент добавляется методом add(), чтобы определить позицию элемента.
Если он возвращает 0, это означает, что два элемента одинаковы, и сохраняется только первый элемент.
Если возвращаемое значение больше 0, это означает, что элемент должен быть размещен после элемента o, указанного в параметре
Если возвращаемое значение меньше 0, это означает, что этот элемент должен быть помещен перед элементом o, указанным в параметре
Поэтому, если вы не вносите никаких изменений в метод compareTo() и запускаете предыдущий проверочный код напрямую, вы обнаружите, что в коллекции есть только 1 элемент:
name:zhangsan,age:20
Затем измените логику метода compareTo() следующим образом:
@Override
public int compareTo(Student o) {
// 排序规则描述如下
// 按照姓名的长度排序,长度短的排在前面,长度长的排在后面
// 如果姓名的长度相同,按字典顺序比较String
// 如果姓名完全相同,按年龄排序,年龄小的排在前面,年龄大的排在后面
int orderByNameLength = this.name.length() - o.name.length();
int orderByName = orderByNameLength == 0 ? this.name.compareTo(o.name) : orderByNameLength;
int orderByAge = orderByName == 0 ? this.age - o.age : orderByName;
return orderByAge;
}
Снова запустив предыдущий проверочный код, вы получите следующий результат:
name:lisi,age:22
name:wangwu,age:24
name:zhaoliu,age:26
name:zhangsan,age:20
name:zhangsan,age:22
6.2 Сортировка компаратором
Сортировка компаратором реализована путем создания нового класса компаратора, наследования интерфейса Comparator и переопределения метода Compare() в интерфейсе.
Примечание. Таким образом, классу Student не нужно реализовывать интерфейс Comparable, а также не нужно переопределять метод compareTo интерфейса.
package collection;
import java.util.Comparator;
public class StudentComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
// 排序规则描述如下
// 按照姓名的长度排序,长度短的排在前面,长度长的排在后面
// 如果姓名的长度相同,按字典顺序比较String
// 如果姓名完全相同,按年龄排序,年龄小的排在前面,年龄大的排在后面
int orderByNameLength = o1.getName().length() - o2.getName().length();
int orderByName = orderByNameLength == 0 ? o1.getName().compareTo(o2.getName()) : orderByNameLength;
int orderByAge = orderByName == 0 ? o1.getAge() - o2.getAge() : orderByName;
return orderByAge;
}
}
Затем измените код, который объявляет studentTreeSet в проверочном коде:
TreeMap<Student, Student> studentTreeMap = new TreeMap<>(new StudentComparator());
Вывод точно такой же, как при использовании естественного порядка.