Java基础二十五(Map)

Map 接口

Map 接口是 Java 集合框架中的一种用于储存键值对映射关系的接口。Map 接口提供了一种通过键来访问值的方式,其中每一个键都是唯一的,值可以重复。

public interface Map<K,V>

Map 接口的主要特征如下:

  1. 键唯一性:Map 中的键是唯一的,同一个键只能对应一个值。如果重复插入相同的键,则后面的值会覆盖前面的值。

  2. 键值对映射:Map 中的每个键都与一个值相关联,它们之间形成了键值对的映射关系。通过键可以获取对应的值。

  3. 无序性:Map 不保证元素的顺序,即不按照添加的顺序保存键值对。如果需要有序遍历,可以使用 SortedMap 的实现类(如 TreeMap)或将 Map 转换为有序的集合。

  4. 允许空键和空值:Map 中可以存储空键(null)和空值(null),但是需要注意空键只能有一个,因为键是唯一的。

  5. 动态调整大小:Map 实现可以根据需要自动调整其内部存储的大小,以容纳更多的键值对。

  6. 遍历操作:Map 提供了多种遍历键、值或键值对的方式,如使用迭代器、keySetvaluesentrySet 等方法。

  7. 实现类:Java 提供了多个 Map 的实现类,常用的有 HashMapTreeMapLinkedHashMap 等,每个实现类都有其特定的性能和用途。

Map 接口常用的方法:

方法 返回值 描述
clear() void 删除所有的映射
containsKey(Object key) boolean Map中是否有这个key
containsValue(Object value) boolean Map是否有这个value
entrySet() Set> 返回包含的映射的Set
get(Object key) V 根据key返回对应的value
isEmpty() boolean Map是否为空
keySet() Set 返回Map中所有key的Set
put(K key, V value) V 向Map添加映射
putAll(Map m) void 将指定Map中的映射复制到此映射
remove(Object key) V 如果key存在,删除映射
remove(Object key, Object value) boolean 当key、value映射存在时,删除
replace(K key, V value) boolean 当key存在时,替换内容
size() int Map中映射的数量
values() Collection 返回所有value的集合

说明:

  • Map 接口是键值对映射表的接口,具有键和值的一一映射关系。
  • Map 中不能有重复的键,每个键只能映射唯一的值。
  • 当使用对象作为键时,需要重写 equalshashCode 方法。
  • Map 提供了多个操作方法,如判断是否包含指定键或值、添加、删除映射等。
  • entrySet() 方法返回一个包含所有键值映射的 Set 集合。
  • keySet() 方法返回一个包含所有键的 Set 集合。
  • values() 方法返回一个包含所有值的集合。

JDK9 提供了创建不可修改 Map 对象的方法:

  1. Map.of
  2. Map.ofEntries
  3. Map.copyOf

1. HashMap

public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, Serializable

HashMapMap 接口的一个实现类,它使用哈希表来存储键值对。

特点:

  • HashMap 允许键和值为null
  • 键不允许重复,值可以重复,但是每个键只能映射到一个值。
  • 查找、插入和删除操作的时间复杂度接近常数时间(O(1))
  • 非线程安全
  • 默认容量 16,默认负载因子 0.75,扩容是 2 倍旧的容量
    • 默认的初始容量为16,意味着在创建 HashMap 对象时,底层会初始化一个长度为16的数组用于存储键值对。
    • 负载因子(load factor)是一个用于控制 HashMap 扩容的参数。默认的负载因子为0.75。负载因子表示当哈希表中的键值对数量达到容量与负载因子乘积的阈值时,就会进行扩容。例如,在默认情况下,当哈希表中的键值对数量达到了 16 * 0.75 = 12 时,就会进行扩容操作。
  • 在存储数据时, keyhash 计算调用的是 HashMap 中的 hash 方法
  • 内部采用 数组 + 链表实现 , JDK 8 及以后版本增加红黑树的支持。

常用方法:

  • put(key, value):向 HashMap 中添加一个键值对。
  • get(key):根据键获取对应的值。
  • remove(key):根据键删除对应的键值对。
  • containsKey(key):判断是否包含指定的键。
  • containsValue(value):判断是否包含指定的值。
  • size():返回 HashMap 中键值对的数量。
  • clear():清空 HashMap 中的所有键值对。

示例代码:

import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        // 创建一个HashMap对象
        HashMap<String, Integer> hashMap = new HashMap<>();

        // 添加键值对
        hashMap.put("apple", 1);
        hashMap.put("banana", 2);
        hashMap.put("orange", 3);

        // 获取值
        int value = hashMap.get("apple");
        System.out.println("Value for key 'apple': " + value);

        // 判断是否包含指定键
        boolean containsKey = hashMap.containsKey("banana");
        System.out.println("Contains key 'banana': " + containsKey);

        // 删除键值对
        hashMap.remove("orange");

        // 获取键值对数量
        int size = hashMap.size();
        System.out.println("Size of HashMap: " + size);

        // 清空HashMap
        hashMap.clear();
        System.out.println("HashMap is empty: " + hashMap.isEmpty());
    }
}

请注意,本接口不保证映射的顺序,如果需要按特定顺序遍历键值对,可以使用 LinkedHashMap 类来代替。

构造方法
HashMap 类提供了多个构造方法,下面列出了常用的几种:

  1. HashMap():创建一个空的 HashMap 对象,初始容量为16,加载因子为0.75。
  2. HashMap(int initialCapacity):创建一个空的 HashMap 对象,指定初始容量,加载因子为0.75。
  3. HashMap(int initialCapacity, float loadFactor):创建一个空的 HashMap 对象,指定初始容量和加载因子。
  4. HashMap(Map m):创建一个新的 HashMap 对象,将指定 Map 中所有键值对复制到创建的 HashMap 中。

示例代码:

import java.util.HashMap;
import java.util.Map;

public class HashMapExample {
    public static void main(String[] args) {
        // 创建一个空的HashMap对象,默认初始容量为16,加载因子为0.75
        HashMap<String, Integer> hashMap1 = new HashMap<>();

        // 创建一个初始容量为10的HashMap对象,加载因子为0.75
        HashMap<String, Integer> hashMap2 = new HashMap<>(10);

        // 创建一个初始容量为10,加载因子为0.5的HashMap对象
        HashMap<String, Integer> hashMap3 = new HashMap<>(10, 0.5f);

        // 创建一个包含键值对的Map对象
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        map.put("orange", 3);

        // 将map中的键值对复制到HashMap对象中
        HashMap<String, Integer> hashMap4 = new HashMap<>(map);
    }
}

HashMap 中 put 过程
Java基础二十五(Map)_第1张图片

HashMapput(key, value) 方法用于将指定的键值对插入到哈希表中。下面是 put(key, value) 方法的简化实现过程:

  1. 判断是否需要进行扩容操作:

    • 如果当前键值对数量超过了容量与负载因子乘积的阈值,就会进行扩容操作。
    • 扩容操作会创建一个新的更大的数组,并重新计算每个键的哈希值,将键值对重新散列到新的数组中。
  2. 计算键的哈希值:

    • 使用键对象的 hashCode() 方法计算哈希值。
    • 这个哈希值会通过一系列位运算来得到最终的存储位置。
  3. 查找存储位置:

    • 根据计算得到的哈希值,找到在数组中对应的索引位置。
  4. 处理冲突:

    • 如果发生了哈希冲突,即多个键的哈希值相同,此时会使用链表或红黑树等数据结构来存储冲突的键值对。
    • 如果链表长度较长(默认为8,以链表的形式放到后边,长度超过阈值且数组长度大于等于64),会将链表转换为红黑树,以提高查找效率。删除元素时,如果时以红黑树存储的如果节点小于 6 个将会变为链表存储
  5. 插入键值对:

    • 如果找到了对应的存储位置,直接插入键值对。
    • 如果存在相同的键,则用新的值替换旧的值。

示例代码:

public V put(K key, V value) {
    // 判断是否需要扩容
    if (size + 1 > threshold) {
        resize();
    }

    // 计算键的哈希值
    int hash = hash(key);

    // 查找存储位置
    int index = indexFor(hash, table.length);

    // 处理冲突
    for (Entry<K,V> e = table[index]; e != null; e = e.next) {
        if (e.hash == hash && (key == e.key || key.equals(e.key))) {
            V oldValue = e.value;
            e.value = value;
            return oldValue; // 返回旧值
        }
    }

    // 插入新的键值对
    addEntry(hash, key, value, index);
    return null;
}

这只是 put(key, value) 方法的简化实现,实际上还有更多细节和边界情况需要考虑。以上简化的代码只是为了帮助理解 put 操作的基本原理。实际的 HashMap 类中的代码更加复杂,涉及更多细节和优化。

2. TreeMap

public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, Serializable

TreeMap 是 Java 中的一种有序映射(SortedMap)实现,它基于红黑树(Red-Black Tree)数据结构来存储键值对,并保持键的有序性
Java基础二十五(Map)_第2张图片

特点:

  1. 键的有序性:TreeMap 会根据键的自然顺序或自定义比较器来对键进行排序。这使得 TreeMap 的键值对在迭代时可以按照键的顺序访问。

  2. 动态维护有序:当插入、删除或查询操作时,TreeMap 内部会自动调整红黑树的结构来保持键的有序性。因此,在任何时候,TreeMap 的键都是有序的。

  3. 高效的遍历操作:由于 TreeMap 的键是有序的,因此可以根据键的范围进行快速的查找和遍历操作。例如,可以使用 subMap() 方法获取指定范围内的子映射。

  4. 支持自定义排序规则:通过提供自定义的比较器(Comparator),可以在创建 TreeMap 对象时指定键的排序方式。如果没有提供比较器,则使用键的自然顺序(实现了 Comparable 接口的键)。

  5. 非线程安全

  6. 允许 null 值,当 null 作为键时,由于键是有序的,需要使用自定义比较器。

示例代码:
以下是一个示例,演示如何使用自定义比较器来处理 null 键:

import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

public class TreeMapExample {
    public static void main(String[] args) {
        // 自定义比较器,处理 null 键
        Comparator<String> nullComparator = new Comparator<String>() {
            @Override
            public int compare(String str1, String str2) {
                if (str1 == null && str2 == null) {
                    return 0;
                } else if (str1 == null) {
                    return -1;
                } else if (str2 == null) {
                    return 1;
                } else {
                    return str1.compareTo(str2);
                }
            }
        };

        // 使用自定义比较器创建 TreeMap
        TreeMap<String, Integer> treeMap = new TreeMap<>(nullComparator);

        treeMap.put(null, 1);
        treeMap.put("apple", 2);
        treeMap.put("banana", 3);
        treeMap.put("tangerine", 4);

        for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        // 获取第一个和最后一个键
        System.out.println(treeMap.firstKey());  // null
        System.out.println(treeMap.lastKey());  // banana
        System.out.println(treeMap.firstEntry());  // null=1
        System.out.println(treeMap.lastEntry());  // banana=3

        // 获取小于等于指定键的最大值和大于等于指定键的最小值
        System.out.println(treeMap.floorKey("c"));  // banana
        System.out.println(treeMap.ceilingKey("c"));  // tangerine
        System.out.println(treeMap.floorEntry("c"));  // banana=3
        System.out.println(treeMap.ceilingEntry("c"));  // tangerine=4

        // 按照倒叙输出
        System.out.println(treeMap.descendingMap());  // {tangerine=4, banana=3, apple=2, null=1}
        System.out.println(treeMap);  // {null=1, apple=2, banana=3, tangerine=4}
        System.out.println(treeMap.descendingKeySet());  // [tangerine, banana, apple, null]
    }
}

在这个示例中,我们创建了一个自定义比较器 nullComparator 来处理 null 键。它首先检查两个键是否都是 null,然后再处理其他情况。这样,即使有 null 键存在,TreeMap 也能够正确地进行排序并保持有序状态。

3. Hashtable

public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, Serializable

Hashtable 是 Java 中的一种哈希表数据结构,它实现了 Map 接口,提供了键值对的储存和检索功能。

特点:

  1. 线程安全(同步的)
  2. 键值不允许为null
  3. 动态扩容:默认初始容量( initialCapacity )为 11 ,默认负载因子( loadFactor )为 0.75f,扩容方式是旧容量的2倍 +1
    • 为了均匀分布,降低冲突率
  4. 不保证顺序
  5. 数组 + 链表方式存储实现Hash表存储
  6. 添加元素:
    • 如果 hash一样 equalsfalse 则将当前值添加到链表头
    • 如果 hash 一样 equalstrue 则使用当前值替换原来的值 ( key 相同)

Java基础二十五(Map)_第3张图片

构造方法

方法 描述
Hashtable() 构造一个新的,空的散列表,默认初始容量为 11 和负载因子为 0.75。
Hashtable(int initialCapacity) 构造一个新的,空的哈希表,具有指定的初始容量和默认负载因子为 0.75。
Hashtable(int initialCapacity, float loadFactor) 构造一个新的,空的哈希表,具有指定的初始容量和指定的负载因子。
Hashtable(Map t) 构造一个新的哈希表,其映射与给定的 Map 相同。

HashMap 与 Hashtable 中 put 过程比较

  1. 线程安全性:Hashtable 是线程安全的,而 HashMap 不是。Hashtable 的所有方法都是同步的,可以在多线程环境下安全使用,但同时也会带来一定的性能开销。而 HashMap 不提供线程安全的保证,需要在多线程环境下自行处理同步。

  2. 允许键或值为空:Hashtable 不允许键或值为空(null),如果尝试插入空键或值,会抛出 NullPointerException。而 HashMap 允许键和值都为空。

  3. 继承关系:Hashtable 是早期的 Java 类,实现了 Dictionary 接口,并继承自 Dictionary 类。而 HashMap 是 Java Collections Framework 的一部分,实现了 Map 接口,并继承自 AbstractMap 类。

  4. 同步策略:Hashtable 使用 synchronized 关键字来实现线程安全。而 HashMap 在默认情况下不进行同步,可以通过 Collections.synchronizedMap 方法将 HashMap 转换为线程安全的版本。

在 put 过程中,Hashtable 和 HashMap 的基本逻辑是相同的,都是根据键的哈希码计算索引位置,解决冲突,并插入键值对。区别主要体现在线程安全性和对空键或值的处理上。因此,在单线程环境下,如果不需要线程安全性,推荐使用 HashMap,它具有更好的性能。

示例代码:

import java.util.Hashtable;

public class HashtableExample {
    public static void main(String[] args) {
        Hashtable<String, Integer> hashtable = new Hashtable<>();
        // 添加键值对
        hashtable.put("one", 1);
        hashtable.put("two", 2);
        hashtable.put("three", 3);
        hashtable.put("four", 4);

        // 获取键值对
        System.out.println(hashtable.get("one"));  // 1
        // getOrDefault获取值
        System.out.println(hashtable.getOrDefault("one", 2));  // 1
        System.out.println(hashtable);  // {two=2, one=1, three=3, four=4}

        // 判断是否包含指定的值或键
        System.out.println(hashtable.contains(4));  // true,是否包含值
        System.out.println(hashtable.containsKey("one"));  // true,是否包含键
        System.out.println(hashtable.containsValue(4));  // true,是否包含值

        // 遍历键值对
        for (String key : hashtable.keySet()) {
            System.out.println(key + " : " + hashtable.get(key));
        }
    }
}

你可能感兴趣的:(Java,java,开发语言)