1. Map接口常用方法

方法 描述
put(K key, V value) 添加或更新键值对
putAll(Map<? extends K, ? extends V> m) 将指定映射中的所有键值对复制到当前映射中
get(Object key) 获取指定键对应的值
containsKey(Object key) 检查映射中是否包含指定键
containsValue(Object value) 检查映射中是否包含指定值
remove(Object key) 移除指定键对应的键值对
remove(Object key, Object value) 仅在键的当前映射值等于指定值时移除该键的条目
size() 返回映射中键值对的数量
isEmpty() 检查映射是否为空
clear() 清空所有键值对
keySet() 返回包含所有键的集合
values() 返回包含所有值的集合
entrySet() 返回包含所有键值对的 Set 视图
public class MapDemo {
    public static void main(String[] args) {
        // 创建一个 HashMap
        Map<String, Integer> map = new HashMap<>();

        // 1. put(K key, V value) - 添加键值对
        map.put("Apple", 10);
        map.put("Banana", 20);
        map.put("Cherry", 30);
        System.out.println("After put: " + map);

        // 2. putAll(Map<? extends K, ? extends V> m) - 添加多个键值对
        Map<String, Integer> anotherMap = new HashMap<>();
        anotherMap.put("Date", 40);
        anotherMap.put("Elderberry", 50);
        map.putAll(anotherMap);
        System.out.println("After putAll: " + map);

        // 3. get(Object key) - 获取值
        System.out.println("Value for 'Banana': " + map.get("Banana"));

        // 4. containsKey(Object key) - 检查是否包含键
        System.out.println("Contains key 'Cherry': " + map.containsKey("Cherry"));

        // 5. containsValue(Object value) - 检查是否包含值
        System.out.println("Contains value 50: " + map.containsValue(50));

        // 6. remove(Object key) - 移除键值对
        map.remove("Apple");
        System.out.println("After remove 'Apple': " + map);

        // 7. remove(Object key, Object value) - 条件移除键值对
        map.remove("Banana", 20);
        System.out.println("After remove 'Banana' with value 20: " + map);

        // 8. size() - 获取键值对数量
        System.out.println("Size of map: " + map.size());

        // 9. isEmpty() - 检查是否为空
        System.out.println("Is map empty: " + map.isEmpty());

        // 10. clear() - 清空映射
        map.clear();
        System.out.println("After clear: " + map);

        // 重新填充映射以演示其他方法
        map.put("Fig", 60);
        map.put("Grape", 70);
        map.put("Honeydew", 80);

        // 11. keySet() - 获取所有键
        Set<String> keys = map.keySet();
        System.out.println("Keys: " + keys);

        // 12. values() - 获取所有值
        Collection<Integer> values = map.values();
        System.out.println("Values: " + values);

        // 13. entrySet() - 获取所有键值对
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        System.out.println("Entries:");
        for (Map.Entry<String, Integer> entry : entries) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }
    }
}

2. HashMap

2.1 源码分析(关于EntrySet的分析)

  • HashSet的底层就是HashMap,因此有关HashMap的扩容机制、树化条件不在赘述
  • HashMap有字段transient Set<Map.Entry<K,V>> entrySet;
  • 在debug模式,跟踪put()方法时,会发现代码没有涉及到entrySet,却能在监控区看到entrySet中有数据
    ![[Pasted image 20250118173337.png]]
  • 这值得研究!!!

为什么entrySet中会有数据?

这就不得不提entrySet到底是什么了。

entrySet 实际上是 HashMap 的一个视图,它并不存储实际的数据,而是基于 HashMap 的内部数据结构(即存储键值对的数组和链表)动态生成的。

也就是说,entrySet本身不存储数据,当访问entrySet的数据时,entrySet会从table中动态的查询数据,就好像数据本身就存储在entrySet中一样。

在debug时,idea访问entrySet拿到了数据,并展示在了监控区,让我们以为entrySet中存储了数据。

entrySet是如何获取数据的?

  • 首先,HashMap.EntrySet类继承AbstractSet类,继承AbstractCollection类
final class EntrySet extends AbstractSet<Map.Entry<K,V>>{}
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {}
  • 对下面的程序进行调试,断点在 System.out.println(entry.toString());
public static void main(String[] args) {  
    HashMap<String, Integer> map = new HashMap<>();  
    map.put("a", 1);  
    Set<Map.Entry<String, Integer>> entry = map.entrySet();  
    System.out.println(entry.toString());  
}
  • 从该断点强制不如,会进入到AbstractCollection.toString()方法
public String toString() {  
    Iterator<E> it = iterator();  
    if (! it.hasNext())  
        return "[]";  
  
    StringBuilder sb = new StringBuilder();  
    sb.append('[');  
    for (;;) {  
        E e = it.next();  
        sb.append(e == this ? "(this Collection)" : e);  
        if (! it.hasNext())  
            return sb.append(']').toString();  
        sb.append(',').append(' ');  
    }  
}
  • 在该方法中,会调用iterator()方法,强制步入,将进入到了HashMap.EntrySet.iterator()方法
  • 该方法会返回一个EntryIterator对象,而HashMap.EntryIterator继承HashMap.HashIterator,并实现了Iterator接口
  • 调用EntryIterator的无参构造,将进入HashIterator的无参构造
public final Iterator<Map.Entry<K,V>> iterator() {  
    return new EntryIterator();  
}

final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> {}
  • 在HashIterator的无参构造中,会将HashMap的table赋值给t HashMapNode<K,V>[] t = table;
  • 此时,答案就已经呼之欲出了
HashIterator() {  
    expectedModCount = modCount;  
    Node<K,V>[] t = table;  
    current = next = null;  
    index = 0;  
    if (t != null && size > 0) { // advance to first entry  
        do {} while (index < t.length && (next = t[index++]) == null);  
    }  
}
  • 接下来,会循环调用hashNext()方法和next()方法,都是使用t中的数据,也就是table中的数据
  • 这就印证了entrySet会从table中动态的查询数据
public final boolean hasNext() {  
    return next != null;  
}  
  
final Node<K,V> nextNode() {  
    Node<K,V>[] t;  
    Node<K,V> e = next;  
    if (modCount != expectedModCount)  
        throw new ConcurrentModificationException();  
    if (e == null)  
        throw new NoSuchElementException();  
    if ((next = (current = e).next) == null && (t = table) != null) {  
        do {} while (index < t.length && (next = t[index++]) == null);  
    }  
    return e;  
}

同样的,keySet()、values()获取数据类似的过程

2.2 HashMap的几种遍历方式

public static void main(String[] args) {  
    HashMap<String, Integer> map = new HashMap<>();  
    map.put("a", 1);  
    map.put("b", 2);  
    map.put("c", 3);  
    map.put("d", 4);  
    map.put("e", 5);  
  
    Set<Map.Entry<String, Integer>> entries = map.entrySet();  
    // 1.  
    Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();  
    while (iterator.hasNext()) {  
        System.out.println(iterator.next());  
    }  
    // 2.  
    for (Map.Entry<String, Integer> entry : entries) {  
        System.out.println(entry.toString());  
    }  
    // 3.  
    entries.forEach(entry -> {  
        System.out.println(entry.toString());  
    });  
  
    // -------------------------------  
    Set<String> keySet = map.keySet();  
    // 1.  
    Iterator<String> keyIterator = keySet.iterator();  
    while (keyIterator.hasNext()) {  
        String key = keyIterator.next();  
        System.out.println(key + ":" + map.get(key));  
    }  
    //2.  
    for (String key : keySet) {  
        System.out.println(key + ":" + map.get(key));  
    }  
    //3.  
    keySet.forEach(key -> {  
        System.out.println(key + ":" + map.get(key));  
    });  
    // -------------------------------  
    Collection<Integer> collection = map.values();  
    //1.  
    Iterator<Integer> values = collection.iterator();  
    while (values.hasNext()) {  
        System.out.println(values.next());  
    }  
    // 2.  
    for (Integer value : collection) {  
        System.out.println(value);  
    }  
    // 3.  
    collection.forEach(System.out::println);  
  
}

3. HashTable的使用

  • 线程安全
  • 不允许键或值为null
  • 初始容量为11,临界值为容量*0.75
  • 扩容方案:newCapacity = oldCapacity * 2 + 1
    • 例如:从 11 扩容到 23,再扩容到 47。

4. TreeMap的使用

  • 默认排序TreeMap 默认按照键的自然顺序(由键实现的 Comparable 接口定义)排序,例如数字从小到大,字符串按字典顺序排序。
  • 自定义排序:可以通过在构造 TreeMap 时传入 Comparator 对象,自定义排序规则。
  • TreeMap 不允许键为 null,否则会抛出 NullPointerException
    • 这是因为在排序时,null 键无法进行比较。
    • 值可以为 null,没有限制。