Java 入门指南:Map 接口

news2024/11/15 17:57:23

Map 接口是 Java 集合框架中的一个接口,它表示了一种键值对的映射关系Map 接口提供了一种以键为索引的数据结构,通过键可以快速查找对应的值。在 Map 中,每个键只能对应一个值,键是唯一的,但值可以重复

常用的实现类有 HashMapTreeMapLinkedHashMap 等。

![[Map 接口概览.png]]

Map 常用方法

  • put(key, value):将指定的键值对添加到 Map 中。

  • putAll(Map<? extends K, ? extends V> m):将指定的映射中的所有键值对添加到当前的 Map

  • get(key):根据键获取对应的值。

  • containsKey(key):判断指定的键是否存在于 Map 中。

  • containsValue(value):判断指定的值是否存在于 Map 中。

  • remove(key):根据键移除对应的键值对。

  • size():获取 Map 中键值对的数量。

  • keySet():获取 Map 中所有键,存入到集合。

  • values():获取 Map 中所有值,存入到集合。

  • Set<Map.Entry<K,V>> entrySet():获取 Map 中所有键值对,存入到集合。

Set<Map.Entry(String, Integer)> entries = map.entrySet();
  • isEmpty():判断 Map 是否为空

  • clear():清除 Map 中所有数据

Map 中的排序方法

在 Java 中,Map 接口本身并没有直接提供 Comparator 接口相关的方法。Comparator 接口主要用于对集合中的元素进行比较排序,而 Map 接口表示的是键值对的集合,其排序通常是基于键或值进行的。

Map 进行排序,并且希望自定义排序逻辑,可以通过以下几种方式来实现:

  1. 使用 TreeMap:TreeMap 实现了 SortedMap 接口,它根据键的自然顺序或指定的 Comparator 对键进行排序。可以在创建 TreeMap 对象时,通过传入自定义的 Comparator 对象来实现排序

  2. 转换为 List 进行排序:先将 Map 的键值对转换成一个 List,然后使用 Collections.sort() 方法对 List 进行排序,并且可以提供自定义的 Comparator 对象。

基于 键 排序

使用 comparingByKey() 方法可以很方便地对 Map 的键进行排序

comparingByKey()java.util.Map 类中的一个静态方法,在 Java 8 中引入的函数式接口 java.util.Comparator 中有一个 comparingByKey() 方法的默认实现。

comparingByKey() 方法返回一个基于键的比较器(Comparator),用于对 Map 中的进行排序。它可以用于对 Map 的键值对进行排序操作。

public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
	 Objects.requireNonNull(cmp);
	 return (Comparator<Map.Entry<K, V>> & Serializable)
		 (c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
 }

实例:

Map<String, Integer> sortedMap = new TreeMap<>(Map.Entry.comparingByKey());
sortedMap.putAll(map);

基于 值 排序

使用 comparingByValue() 方法可以方便地对 Map 的值进行排序

comparingByValue()java.util.Map 类中的一个静态方法,在 Java 8 中引入的函数式接口 java.util.Comparator 中有一个 comparingByValue() 方法的默认实现。

comparingByValue() 方法返回一个基于值的比较器(Comparator),用于对 Map 中的进行排序。它可以用于对 Map 的键值对进行排序操作。

public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super V> cmp) {
	 Objects.requireNonNull(cmp);
	 return (Comparator<Map.Entry<K, V>> & Serializable)
		 (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
 }

实例:

List<Map.Entry<String, Integer>> sortedList = new ArrayList<>(map.entrySet());
        sortedList.sort(Map.Entry.comparingByValue());

自定义比较器

comparingByValuecomparingByKey 可以作为自定义比较器:

// 使用 comparingByKey 方法创建自定义比较器
        Comparator<String> keyComparator = 
                Comparator.comparingByKey();

也可以自定义比较器传入两方法中,使用自行定义的比较器对 Map 中的键值对进行排序:

Comparator<String> keyComparator = (k1, k2) -> {
	// 自定义比较逻辑,这里假设按照键的长度进行比较
	return Integer.compare(k1.length(), k2.length());
};
        
// 使用自定义的比较器对键进行排序
map.entrySet()
	// 将集合转换为流
	.stream()
	.sorted(Map.Entry.comparingByKey(keyComparator))
	.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
    }

Map 中的默认方法

  • default V getOrDefault(Object key, V defaultValue)
    通过键获取一个值,如果不存在设置默认值 defaultValue

  • default void forEach(BiConsumer<? super K, ? super V> action)遍历 Map,通过 函数式接口的 lambda 表达式实现遍历操作:打印、求和等

  • void replaceAll(BiFunction<? super K, ? super V, extends V> function) :替换所有的键值对

  • default V putIfAbsent(K key, V value)

    如果不存在键值对就添加键值对,否则就不添加

  • default boolean remove(Object key, Object value)

    移除键值对,键值对必须全部匹配,否则失败

  • default boolean replace(K key, V oldValue, V newValue)

    类似于乐观锁,如果根据键值得到的键值和期望的旧值oldValue 相等,就替换为新值,否则失败

  • default V replace(K key, V value) 替换已经存在的键值对

  • default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)

    如果对应键不存在键值对,则使用自定义 lambda [Function<K,V>] 指定根据键返回值,并且存放入 Map;如果返回 null,就不放入 Map ,并且返回 null

  • default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

    如果对应键存在键值对,使用自定义 lambda [BiFunction<T,U,R>],根据key 和 U 生成 对应值,并将键值对放入 Map;如果不存在键值对,返回null;如果生成对应值为 null ,会将 key 对应键值对删除

  • default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

    根据键(key)获取旧值,自定义 lambda [BiFunction<T,U,R>] ,通过 key 和 旧值 生成新值,将新键值对放入Map;如果生成新值为 null,并且旧值存在则删除旧的键值对,旧的键值对不存在就返回 null

  • default V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction)

    如果存在旧的键值对,设置新值为新的键值对,并返回新值(value);如果九的键值对不存在,自定义 lambda [BiFunction<T,U,R>],根据旧值和 value 生成新值,新值不为 null 就将新的键值对放入Map,新的键值为 null 就删除旧的键值对

Map.Entry

Map.EntryMap 接口的内部接口,它表示 Map 中的一个键值对。在 Map 中,每个键值对都由一个 Map.Entry 对象来表示。

Map.Entry 接口定义了以下几个常用方法:

  1. getKey():获取当前键值对的键。
  2. getValue():获取当前键值对的值。
  3. setValue(V value):设置当前键值对的值。

通过 Map.Entry 接口,可以方便地遍历 Map 中的键值对。

for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    // 修改键值对的值
    entry.setValue(value * 2);
}

Map 常见实现类

Map 接口的实现类常用的有 HashMapTreeMapLinkedHashMap 等。

HashMap

HashMap 是 Java 集合框架中的一个类,它实现了 Map 接口,是 Map 接口使用频率最高的实现类,允许存储键值对,并提供了快速的插入、访问和删除操作。

HashMap 使用哈希表数据结构来存储键值对构成的 Map.Entry通过键的哈希值来确定其在内部数组中的位置,以实现快速的检索性能。

HashMap的主要特点
  1. 键值对:HashMap 存储的数据是键值对,每个键唯一。可以根据键快速获取对应的值,但是不能保证键值对的顺序

  2. 线程不安全HashMap 不是线程安全的类,多个线程同时访问和修改 HashMap 可能导致数据不一致或异常。如果需要在多线程环境下使用,可以考虑使用 ConcurrentHashMap

    在并发环境中,可以使用 concurrentHashMap 或其他线程安全的实现类

  3. null 键和值:HashMap 允许存储 null 键(只能有一个)和 null 值(可以有多个)。

  4. HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。

  5. HashMap 判断两个 value 相等的标准是:两个 value 通过 equals() 方法返回 true。

HashMap 的四种构造方法
  1. 创建一个空的 HashMap 对象,默认初始容量为 16,负载因子为 0.75。
HashMap()
  1. 创建一个具有指定初始容量的 HashMap 对象。
HashMap(int initialCapacity)

初始容量是指 HashMap 最初可以存储的键值对数量上限。如果不确定初始容量,可以根据数据量和负载因子进行估算。

  1. 创建一个具有指定初始容量和加载因子的 HashMap 对象。
HashMap(int initialCapacity, float loadFactor)
  1. 创建一个包含指定映射的 HashMap 对象,其中映射的键值对会被复制到新的 HashMap 中。
HashMap(Map<? extends K, ? extends V> m)
  1. 用于创建一个具有指定初始容量、加载因子和访问顺序的 HashMap 对象。
HashMap(int initialCapacity, float loadFactor, boolean accessOrder)

accessOrder:指定的访问顺序,为 true 表示按照访问顺序(LRU 链表顺序)进行迭代,即最近访问的元素会放在链表尾部;为 false 表示按照插入顺序进行迭代。

访问顺序的设定对于实现 LRU(最近最少使用)缓存等应用场景非常有用,可以方便地实现基于访问频率的数据清除策略。

HashMap 的存储结构

JDK 1.7及以前版本:HashMap数组+链表 结构(即为链地址法)

数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)

JDK 1.8版本发布以后:HashMap数组+链表+红黑树 实现。

当链表长度大于等于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。

JDK 1.7及之前

当实例化一个 HashMap 时,系统会创建一个长度为 Capacity 的 Entry 数组,这个长度在哈希表中被称为容量(Capacity),数组中可以存放元素的位置称为“”(bucket),每个 bucket 都有自己的索引,系统可以根据索引快速的查找 bucket 中的元素。

每个 bucket 中存储一个元素,即一个 Entry 对象,每一个 Entry 对象可以带一个引用变量,用于指向下一个元素。在一个桶中,就有可能生成一个Entry链。新添加的元素作为链表的 head

JDK 1.8及之后

当实例化一个 HashMap 时,会初始化 initialCapacityloadFactor, 在 put 第一对映射关系时,系统会创建一个长度为 initialCapacityNode 数组。数组每个 bucket 都有自己的索引,系统可以根据索引快速的查找 bucket 中的元素。

每个 bucket 中存储一个元素,即一个 Node 对象,每一个 Node 对象可以带一个引用变量 next,用于指向下一个元素,在一个桶中,就有可能生成一个 Node 链。

也可能是一个个 TreeNode 对象,每一个 TreeNode 对象可以有两个叶子结点 left 和 right。在一个桶中,就有可能生成一个 TreeNode 树。而新添加的元素作为链表的 last,或树的叶子结点。

HashMap 扩容机制

HashMap 中的元素越来越多的时候,hash冲突的几率也就越来越高,

因为数组的长度是固定的。为了提高查询的效率,就要对HashMap的数组进行扩容

HashMap 数组扩容之后,最消耗性能的点 resize() 就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去。

JDK 1.7 及之前

HashMap 中的元素个数超过数组大小 loadFactor(数组总大小length,不是数组中个数 size)时,就会进行数组扩容。

默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为 16,那么当 HashMap 中元素个数超过 16 * 0.75 = 12threshold 值,也叫做临界值)的时候,就把数组的大小扩展为 2 * 16 = 32,即扩大一倍,然后重新计算每个元素在数组中的位置

这是一个非常消耗性能的操作,如果已经预知 HashMap 中元素的个数,那么预设元素的个数能够有效的提高 HashMap 的性能。

JDK 1.8及之后

HashMap 中的元素个数超过数组大小(length,不是数组中个数size) loadFactor 时,就会进行数组扩容。

默认情况下,当 HashMap 中元素个数超过 16 * 0.75 = 12(threshold值)的时候,就把数组的大小扩展为 2 * 16 = 32,即扩大一倍,然后重新计算每个元素在数组中的位置。

HashMap 中的其中一个链的对象个数如果达到了 8 个,此时如果capacity 没有达到 64,那么 HashMap 会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由 Node 变成 TreeNode 类型。

(当数组指定索引位置的链表长度 >8 时,且 map 中的数组的长度 >64 时,此索引位置上的所有key-value对使用红黑树进行存储。)

如果当映射关系被移除后,下次 resize 方法时判断树的结点个数低于6个,也会把树再转为链表。

HashMap 使用示例
import java.util.HashMap;
import java.util.Map;

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

        // 添加键值对
        map.put("Alice", 25);
        map.put("Bob", 30);
        map.put("Charlie", 35);

        // 访问键值对
        System.out.println("Age of Alice: " + map.get("Alice"));  // 输出: Age of Alice: 25

        // 更新键值对
        map.put("Alice", 26);
        System.out.println("New age of Alice: " + map.get("Alice"));  // 输出: New age of Alice: 26

        // 检查键是否存在
        System.out.println("Does 'Alice' exist? " + map.containsKey("Alice"));  // 输出: Does 'Alice' exist? true

        // 检查值是否存在
        System.out.println("Does 26 exist? " + map.containsValue(26));  // 输出: Does 26 exist? true

        // 遍历映射中的所有键值对
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }

        // 移除键值对
        map.remove("Bob");
        System.out.println("After removing Bob: " + map);  // 输出: After removing Bob: {Alice=26, Charlie=35}

        // 清空映射
        map.clear();
        System.out.println("Is the map empty? " + map.isEmpty());  // 输出: Is the map empty? true
    }
}

ConcurrentHashMap

ConcurrentHashMap 继承自 AbstractMap ,它是线程安全的哈希表实现,用于存储键值对。它提供了高效的并发操作,多个线程可以同时进行读取和写入操作,而不会发生数据不一致的情况。

ConcurrentHashMap 提供了与 HashMap 相似的接口和方法,主要包括添加、获取、删除元素的操作,以及迭代等。

ConcurrentHashMap 的主要特点
  1. 线程安全性: ConcurrentHashMap 内部使用了分段锁(Segment)机制,将整个哈希表拆分为多个独立的部分,每个部分拥有自己的锁,不同的线程可以同时操作不同的部分,提高了并发性能。

  2. 高并发性: 相较于 HashTableConcurrentHashMap 在多线程并发读写的情况下能够提供更高的性能,因为它允许多个线程同时读取而不需要加锁

  3. 可伸缩性: ConcurrentHashMap 允许在加载因子达到阈值时对其进行自动扩容,从而适应动态变化的数据大小。

  4. 不保证有序性: ConcurrentHashMap 不保证迭代顺序与插入顺序或访问顺序一致。

ConcurrentHashMap 的构造方法
  1. 创建一个默认初始容量为16ConcurrentHashMap 实例。
ConcurrentHashMap()
  1. 创建一个指定初始容量的 ConcurrentHashMap 实例。
ConcurrentHashMap(int initialCapacity)
  1. 创建一个包含指定映射中所有键值对的 ConcurrentHashMap 实例。
ConcurrentHashMap(Map< ? extends K, ? extends V> map)
  1. 创建一个指定初始容量和加载因子的 ConcurrentHashMap 实例。
ConcurrentHashMap(int initialCapacity, float loadFactor)
  1. 创建一个指定初始容量、加载因子和并发级别的 ConcurrentHashMap 实例。并发级别是哈希表内部的并发更新线程数(预期值)
ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)

并发更新线程实际上是由锁的数量来决定的,而不是由 concurrencyLevel 参数明确限制的。

根据 Oracle 官方文档的建议,如果不确定应该将 concurrencyLevel 设置为多少,可以使用默认值 16,在大多数情况下都能够提供良好的性能。

ConcurrentHashMap 使用示例
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        // 创建一个 ConcurrentHashMap
        Map<String, Integer> map = new ConcurrentHashMap<>();

        // 添加键值对
        map.put("Alice", 25);
        map.put("Bob", 30);
        map.put("Charlie", 35);

        // 访问键值对
        System.out.println("Age of Alice: " + map.get("Alice"));  // 输出: Age of Alice: 25

        // 更新键值对
        map.put("Alice", 26);
        System.out.println("New age of Alice: " + map.get("Alice"));  // 输出: New age of Alice: 26

        // 检查键是否存在
        System.out.println("Does 'Alice' exist? " + map.containsKey("Alice"));  // 输出: Does 'Alice' exist? true

        // 检查值是否存在
        System.out.println("Does 26 exist? " + map.containsValue(26));  // 输出: Does 26 exist? true

        // 遍历映射中的所有键值对
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }

        // 移除键值对
        map.remove("Bob");
        System.out.println("After removing Bob: " + map);  // 输出: After removing Bob: {Alice=26, Charlie=35}

        // 清空映射
        map.clear();
        System.out.println("Is the map empty? " + map.isEmpty());  // 输出: Is the map empty? true
    }
}

LinkedHashMap

LinkedHashMap 是 Java 集合框架中的一个类,它是 HashMap 的一个子类,具有与 HashMap 相同的功能,并且保持了插入顺序或访问顺序。

LinkedHashMap 内部使用一个双向链表来维护键值对的顺序,而不像 HashMap 那样是无序的。这意味着当遍历 LinkedHashMap 时,键值对会按照插入顺序或访问顺序进行迭代。

LinkedHashMap 的主要特点
  1. 保持顺序:LinkedHashMap 保持元素的插入顺序或访问顺序。插入顺序通过默认构造函数实现,而访问顺序需要设置为 accessOrder = true

  2. 继承自 HashMapLinkedHashMap 继承自 HashMap,因此具备了 HashMap 的快速访问和删除操作等特点。但因此LinkedHashMap也是线程不安全

  3. 消耗内存:相比于 HashMapLinkedHashMap 需要额外的内存来维护内部的链表结构。

LinkedHashMap 的五种构造方法
  1. 创建一个空的 LinkedHashMap 对象,默认初始容量为 16,加载因子为 0.75。
LinkedHashMap()
  1. 创建一个具有指定初始容量的 LinkedHashMap 对象。
LinkedHashMap(int initialCapacity)
  1. 创建一个具有指定初始容量和加载因子的 LinkedHashMap 对象。
LinkedHashMap(int initialCapacity, float loadFactor)
  1. 创建一个具有指定初始容量、加载因子和访问顺序的 LinkedHashMap 对象。
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)

accessOrder 参数为 true 表示按照访问顺序进行迭代,为 false 表示按照插入顺序进行迭代。

  1. 创建一个包含指定映射的 LinkedHashMap 对象,其中映射的键值对会被复制到新的 LinkedHashMap 中。
LinkedHashMap(Map< ? extends K, ? extends V> m)
LinkedHashMap 使用示例
import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapExample {
    public static void main(String[] args) {
        // 创建一个 LinkedHashMap
        Map<String, Integer> map = new LinkedHashMap<>();

        // 添加键值对
        map.put("Alice", 25);
        map.put("Bob", 30);
        map.put("Charlie", 35);

        // 访问键值对
        System.out.println("Age of Alice: " + map.get("Alice"));  // 输出: Age of Alice: 25

        // 更新键值对
        map.put("Alice", 26);
        System.out.println("New age of Alice: " + map.get("Alice"));  // 输出: New age of Alice: 26

        // 检查键是否存在
        System.out.println("Does 'Alice' exist? " + map.containsKey("Alice"));  // 输出: Does 'Alice' exist? true

        // 检查值是否存在
        System.out.println("Does 26 exist? " + map.containsValue(26));  // 输出: Does 26 exist? true

        // 遍历映射中的所有键值对
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }

        // 移除键值对
        map.remove("Bob");
        System.out.println("After removing Bob: " + map);  // 输出: After removing Bob: {Alice=26, Charlie=35}

        // 清空映射
        map.clear();
        System.out.println("Is the map empty? " + map.isEmpty());  // 输出: Is the map empty? true
    }
}

TreeMap

TreeMap 实现了 NavigableMap 接口,使用红黑树(Red-Black Tree)数据结构来存储键值对。

TreeMap 可以根据键的自然排序或自定义比较器对键进行排序,并提供了快速的查找、插入和删除操作。

TreeSet 主要特点
  1. 排序:TreeMap 通过键的自然顺序或自定义比较器来对键进行排序,并保持键的有序状态。

  2. 基于红黑树TreeMap 使用红黑树来实现键值对的存储。红黑树是一种自平衡的二叉搜索树,可以保持较好的性能和平衡性。

  3. 线程不安全TreeMap 不是线程安全的数据结构。在多线程环境下,如果多个线程同时并发地对 TreeMap 进行读取和修改操作,可能会导致数据不一致或产生其他不可预测的错误。

TreeMap 的四种构造方法
  1. 创建一个空的 TreeMap 对象,并按照键的自然顺序进行排序。
TreeMap()
  1. 创建一个空的 TreeMap 对象,按照指定的比较器 comparator 进行排序。
TreeMap(Comparator< ? super K> comparator)
  1. 创建一个 TreeMap 对象,并将已存在的map中的键值对加入到新的 TreeMap 中。使用键的自然顺序进行排序。
TreeMap(Map< ? extends K, ? extends V> map)
  1. 创建一个 TreeMap 对象,并将已存在的排序映射表 sortedMap 的键值对加入到新的 TreeMap 中。
TreeMap(SortedMap<K, ? extends V> sortedMap)
TreeMap 使用示例
import java.util.TreeMap;
import java.util.Map;

public class TreeMapExample {
    public static void main(String[] args) {
        // 创建一个 TreeMap
        Map<String, Integer> map = new TreeMap<>();

        // 添加键值对
        map.put("Alice", 25);
        map.put("Bob", 30);
        map.put("Charlie", 35);

        // 访问键值对
        System.out.println("Age of Alice: " + map.get("Alice"));  // 输出: Age of Alice: 25

        // 更新键值对
        map.put("Alice", 26);
        System.out.println("New age of Alice: " + map.get("Alice"));  // 输出: New age of Alice: 26

        // 检查键是否存在
        System.out.println("Does 'Alice' exist? " + map.containsKey("Alice"));  // 输出: Does 'Alice' exist? true

        // 检查值是否存在
        System.out.println("Does 26 exist? " + map.containsValue(26));  // 输出: Does 26 exist? true

        // 遍历映射中的所有键值对
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }

        // 移除键值对
        map.remove("Bob");
        System.out.println("After removing Bob: " + map);  // 输出: After removing Bob: {Alice=26, Charlie=35}

        // 清空映射
        map.clear();
        System.out.println("Is the map empty? " + map.isEmpty());  // 输出: Is the map empty? true
    }
}

HashTable

HashTable 实现了 Map 接口, 用于存储键值对,并根据键的哈希码(hash code)进行查找。HashTable线程安全的,但也因此在性能上较 HashMap 稍慢。

HashTable 主要特点
  1. 哈希表:HashTable 内部使用一个哈希表来存储键值对。当插入或查找键值对时,会根据键的哈希码计算存储位置,以提高查找效率。

  2. 线程安全HashTable 是线程安全的类,这意味着多个线程可以同时读取和修改 HashTable 的内容。为了实现线程安全,HashTable 使用了同步(synchronization)机制,使得在并发环境下能够正确地处理读写操作。

  3. 性能相对较差:HashTable 的所有方法都是同步的,它通过对常用的操作加锁来保证线程安全。在单线程环境下,可以使用 HashMap ,多线程环境下,可以使用 ConcurrentHashMap 替代

HashTable 的四种构造方法
  1. 创建一个空的 HashTable 对象,默认初始容量为 11,加载因子为 0.75。
HashTable()
  1. 创建一个具有指定初始容量的 HashTable 对象。
HashTable(int initialCapacity)
  1. 创建一个具有指定初始容量和加载因子的 HashTable 对象。
HashTable(int initialCapacity, float loadFactor)
  1. 创建一个包含指定映射的 HashTable 对象,其中映射的键值对会被复制到新的 HashTable 中。
HashTable(Map< ? extends K, ? extends V> m)
HashTable 使用示例
import java.util.Hashtable;
import java.util.Map;

public class HashTableExample {
    public static void main(String[] args) {
        // 创建一个 HashTable
        Map<String, Integer> map = new Hashtable<>();

        // 添加键值对
        map.put("Alice", 25);
        map.put("Bob", 30);
        map.put("Charlie", 35);

        // 访问键值对
        System.out.println("Age of Alice: " + map.get("Alice"));  // 输出: Age of Alice: 25

        // 更新键值对
        map.put("Alice", 26);
        System.out.println("New age of Alice: " + map.get("Alice"));  // 输出: New age of Alice: 26

        // 检查键是否存在
        System.out.println("Does 'Alice' exist? " + map.containsKey("Alice"));  // 输出: Does 'Alice' exist? true

        // 检查值是否存在
        System.out.println("Does 26 exist? " + map.containsValue(26));  // 输出: Does 26 exist? true

        // 遍历映射中的所有键值对
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }

        // 移除键值对
        map.remove("Bob");
        System.out.println("After removing Bob: " + map);  // 输出: After removing Bob: {Alice=26, Charlie=35}

        // 清空映射
        map.clear();
        System.out.println("Is the map empty? " + map.isEmpty());  // 输出: Is the map empty? true
    }
}

Properties

Properties 继承自 HashTable ,它用于处理属性文件(.properties)中的键值对数据。属性文件是一种常见的配置文件格式,在 Java 开发中经常被用来存储配置信息。

Properties 主要特点和常用方法
  1. 键值对存储:Properties 使用键值对的方式来存储数据,其中键和值都是字符串类型属性文件中的每一行都表示一个键值对,以 “键=值” 的形式存在。

  2. 加载和保存属性文件:Properties 提供了加载和保存属性文件的方法,可以从属性文件中读取键值对数据,或将键值对数据保存到属性文件中。常用的方法包括:

    • load(InputStream in): 从 输入流 中加载属性文件。

    • load(Reader reader): 从 字符流 中加载属性文件。

    • store(OutputStream out, String comments): 将属性文件保存到输出流中。

    • store(Writer writer, String comments): 将属性文件保存到字符流中。

  3. 获取和设置属性值Properties 提供了一系列方法来获取和设置属性值,如:

    • getProperty(String key): 根据键获取对应的属性值。

    • getProperty(String key, String defaultValue): 根据键获取对应的属性值,若不存在则返回默认值。

    • setProperty(String key, String value): 设置指定键的属性值。

  4. 遍历属性Properties 提供了遍历属性的方法,可以逐个获取所有的键值对。常用的方法有:

    • propertyNames(): 返回包含所有键的枚举对象,可用于迭代遍历。

    • stringPropertyNames(): 返回包含所有键的字符串集合。

    • forEach(BiConsumer< ? super Object, ? super Object> action): 遍历所有的键值对,并执行指定的操作。

Properties 的构造方法
  1. Properties(): 创建一个空的 Properties 对象。

  2. Properties(Properties defaults): 创建一个 Properties 对象,并指定默认的属性。当获取属性值时,如果在当前 Properties 中找不到对应的值,将返回默认属性中指定的值。

Properties defaults = new Properties();
defaults.setProperty("defaultKey", "defaultValue");
Properties properties2 = new Properties(defaults);

Properties 类不常主动实例化,通常会通过加载配置文件来获得 Properties 实例,例如使用 InputStream 来读取属性文件,然后通过 load 方法将属性加载到 Properties 对象中:

![[Pasted image 20231020154024.png]]

Properties 使用示例

假设我们有一个名为 app.properties 的属性文件,内容如下:

database.url=jdbc:mysql://localhost:3306/mydb
database.user=root
database.password=secret
server.port=8080
server.host=localhost

示例代码

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;

public class PropertiesExample {

    public static void main(String[] args) {
        // 读取属性文件
        Properties properties = readProperties("app.properties");
        
        // 输出原始属性
        printProperties(properties);

        // 修改属性文件
        properties.setProperty("database.password", "newpassword");
        properties.setProperty("server.port", "9090");
        
        // 保存修改后的属性到文件
        saveProperties("app.properties", properties);
        
        // 再次读取属性文件验证修改
        Properties updatedProperties = readProperties("app.properties");
        printProperties(updatedProperties);
    }

    /**
     * 读取属性文件到 Properties 对象。
     *
     * @param fileName 属性文件的名称
     * @return 包含属性的 Properties 对象
     */
    private static Properties readProperties(String fileName) {
        Properties properties = new Properties();
        try (FileInputStream fileInputStream = new FileInputStream(fileName)) {
            properties.load(fileInputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return properties;
    }

    /**
     * 将 Properties 对象保存到属性文件。
     *
     * @param fileName 属性文件的名称
     * @param properties 包含属性的 Properties 对象
     */
    private static void saveProperties(String fileName, Properties properties) {
        try (FileOutputStream fileOutputStream = new FileOutputStream(fileName)) {
            properties.store(fileOutputStream, "Updated properties file.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 打印 Properties 对象中的所有属性。
     *
     * @param properties 包含属性的 Properties 对象
     */
    private static void printProperties(Properties properties) {
        properties.list(System.out);
        System.out.println();
    }
}

解析:

  1. 读取属性文件

    • 使用 readProperties 方法加载属性文件到 Properties 对象中。
    • 使用 FileInputStream 打开属性文件。
    • 使用 Properties 对象的 load 方法加载文件内容。
    • 使用 printProperties 方法打印原始属性。
  2. 修改属性文件

    • 使用 setProperty 方法更新属性值。
    • 使用 saveProperties 方法将修改后的属性保存回文件系统。
    • 使用 FileOutputStream 打开属性文件以写入模式。
    • 使用 Properties 对象的 store 方法保存修改后的属性。
  3. 再次读取属性文件验证修改

    • 重新读取属性文件到 Properties 对象中。
    • 使用 printProperties 方法打印更新后的属性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2072025.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在vscode上便捷运行php文件

目录 前言 1. 准备工作 2. 创建文件 3. 下载插件 4.设置访问配置文件 5. 配置默认浏览器 6. 进行验证 前言 对于学习安全的我们来说,部署环境,靶场,和配置环境都是习以为常的一件事情,平时访问靶场都是通过小皮来,今天突想着最近需要对一些漏洞的原理进行研究,所以需要能够…

ESP-WHO C++程序分析基础(七)

以按键部分的程序做为分析基础 先看app_button.hpp文件&#xff0c;文件的路径如下 examples/esp32-s3-eye/main/include/app_button.hpp // AppButton 类&#xff0c;继承自 Subject 类&#xff0c;表示应用程序按钮 首先是先定义了一个 appbutton的按键类&#xff0c;这个…

【计算机组成原理】汇总三、存储系统

三、存储系统&#xff08;存储器层次结构&#xff09; 文章目录 三、存储系统&#xff08;存储器层次结构&#xff09;1.存储器的分类1.1按在计算机中的作用&#xff08;层次&#xff09;❗多级存储结构&#xff08;层次化结构&#xff09;1.2按存储介质1.3按存取方式1.4按信息…

抢单源码修正版,带教程,自动抓取订单,十几种语言可自动切换

亚马逊抢单源码自动抓取订单任务邀请英文,西班牙语可自动切换语言亲测修正版。带完整开源的前后台。 西班牙,英文&#xff0c;巴西&#xff0c;中文&#xff0c;德国&#xff0c;拉法兰西&#xff0c;荷兰&#xff0c;缅甸&#xff0c;Sverige&#xff0c;日本&#xff0c;Trk…

C_02基础学习

c 语言 基础 gcc编译器 作用: 将代码文件编译为可执行文件 分类: 一步到位gcc 要编译的代码文件 -o 生成的可执行文件注意:要编译的代码文件可以是多个-o 生成的可执行文件:可以忽略不写,默认生成a.out文件 分步实现预编译:头文件展示,宏替换,选择型编译gcc -E 要编译的代码…

VMware NET Service在虚拟机关闭后仍然占用CPU - 解决方案

问题 VMware NET Service&#xff08;即vmnat.exe&#xff09;在虚拟机关闭后仍然占用CPU&#xff0c;这是VM 17.5.0 和 VM 17.5.1 软件本身存在的Bug&#xff0c;此问题已在 VM 17.5.2 版本修复&#xff0c;下文介绍解决方案。 时间&#xff1a;2024年8月 解决方案 临时方…

百度网盘网页提示页面过期请刷新 - 解决方案

问题 当打开百度网盘网页的分享链接后&#xff0c;点击下载会提示页面过期请刷新&#xff0c;点击保存到网盘没有响应&#xff0c;刷新后存在同样问题。 原因 这通常是因为浏览器中安装了屏蔽广告的插件&#xff0c;此插件不只拦截了百度网盘的广告&#xff0c;还拦截了一部…

零基础构建 AI 大模型数字人:开启智能交互新时代

人工智能技术的飞速发展&#xff0c;数字人正逐渐成为连接虚拟与现实世界的桥梁。无论是作为客户服务代表、教育助手还是娱乐伙伴&#xff0c;数字人都以其独特的方式丰富着我们的生活。今天&#xff0c;我们将介绍一个基于Dify生态系统的开源数字人技术框架——awesome-digita…

iis部署服务时,发现只能进行get请求,无法发起post、put请求

问题描述&#xff1a; iis部署服务时&#xff0c;发现只能进行get请求&#xff0c;无法发起post、put请求 问题原因&#xff1a; iis部署时&#xff0c;webDAV模块限制 解决方法&#xff1a; 1.搜索【服务器管理器】 2.点击【删除角色功能】 3.选中WebDAV&#xff0c;点…

MinIO实战攻略:轻松构建私有云存储解决方案

OSS 简介 OSS&#xff08;Object Storage Service&#xff09;通常指的是对象存储服务&#xff0c;它是一种数据存储架构&#xff0c;用于存储和检索非结构化数据&#xff0c;如图片、视频、文档和备份等。对象存储服务与传统的块存储和文件存储不同&#xff0c;它将数据作为对…

用户画像标签服务设计

背景 用户画像中不论是实时标签还是离线标签&#xff0c;对需要对外提供查询服务&#xff0c;以便外部接口可以重新用户的标签&#xff0c;本文就来看一下用户标签服务的设计 用户标签服务设计 不论是离线标签还是实时标签&#xff0c;我们都需要先把他们从hive表或者实时re…

OpenCV(第二关--读取图片和摄像头)实例+代码

以下内容&#xff0c;皆为原创&#xff0c;制作不易&#xff0c;感谢大家的关注和点赞。 一.读取图片 我们来读取图片&#xff0c;当你用代码读取后&#xff0c;可能会发现。怎么跟上传的图片颜色有些许的不一样。因为OpenCV的颜色通道是BGR&#xff0c;而我们平常用的matplotl…

华为云通过自定义域名访问桶内对象

问题&#xff1a;通过将自定义域名绑定至OBS桶实现在线预览文件 例如index.html入口文件 且记 自定义域名绑定暂时不支持HTTPS访问方式&#xff0c;只支持HTTP访问方式 自定义域名就先不用部署https证书。 配置完毕之后&#xff0c;将obs桶设置为公开的即可访问 如何在浏览…

若依代码生成器生成的界面查询和导出突然报错了

之前用的好好的&#xff0c;查询的时候也有数据&#xff0c;但是把参数给分页插件的时候就报错了&#xff0c;我忘了啥错误了&#xff0c;很奇怪。 ha在对应Mapper上加上&#xff1a;CacheNamespace注解&#xff0c;完。 Mapper CacheNamespace public interface BaseGoodsMa…

使用Python做一个脚本自动化机器人(二)

刚发现一个好用的Python库DrissionPage&#xff0c;使用该库不区分浏览器&#xff0c;也无需下载driver文件。 import logging from DrissionPage import WebPage from DrissionPage import ChromiumPage,ChromiumOptionsclass BaiduPage():# 创建对象page ChromiumPage()# 访…

SpringBoot项目定义Bean常见方式

1. spring原生的xml 配置bean 现在几乎淘汰&#xff0c;忽略&#xff01;&#xff01; 2. Component 及其衍生注解 &#xff08;Controller、Service、Repository&#xff09; Component public class Cat { }3. Configuration Bean Configuration public class AnimalConf…

【OpenGL】xcode+glfw画三角形

环境搭建 1. 执行brew install glfw 2. 项目中Build Settings中header Search Paths中添加glfw的include路径 3. 项目中Build Phases中的Link Binary With Libraries中添加glfw的lib文件&#xff08;路径/opt/homebrew/Cellar/glfw/3.4/lib/libglfw.3.4.dylib&#xff09;及…

21.缓存穿透

缓存穿透 客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会到达数据库。会造成数据库宕机。 解决方案 1.缓存空对象 例如查询数据的id&#xff0c;发现数据库中没有&#xff0c;那么就在redis中缓存空对象。但是会有额…

springboot项目读取 resources 目录下的文件的9种方式

1. 使用 ClassLoader.getResourceAsStream() 方法 InputStream inputStream getClass().getClassLoader().getResourceAsStream("file.txt"); 2. 使用 Class.getResourceAsStream() 方法 InputStream inputStream getClass().getResourceAsStream("/file.txt&…

安达发|如何实现陶瓷产业智能化生产计划排单?

随着工业4.0的兴起&#xff0c;智能化生产已成为制造业转型升级的关键。陶瓷产业作为一个历史悠久且技术成熟的行业&#xff0c;面临着生产效率提升和成本控制的双重挑战。智能化生产计划排单作为提高生产效率、降低资源浪费的重要手段&#xff0c;对于陶瓷产业的现代化转型至关…