杰瑞科技汇

Java的Collection框架如何高效使用?

总体概览:Collection 框架是什么?

Collection 框架是 Java 提供的一组接口、类和算法,位于 java.util 包下,它的主要目的是以统一、高效的方式管理一组对象(也称为元素)。

Java的Collection框架如何高效使用?-图1
(图片来源网络,侵删)

你可以把它想象成一个“工具箱”,里面有各种不同类型的“容器”(集合),每种容器都有其特定的用途和性能特点,以满足不同的业务场景需求。

核心构成:

  1. 接口:代表集合的抽象数据类型,是操作集合的“契约”。List, Set, Queue
  2. 实现类:是接口的具体实现,提供了数据存储的具体方式。ArrayList, LinkedList, HashSet, HashMap
  3. 算法:是集合框架中提供的一些静态方法,用于执行有用的操作,如排序、查找等。Collections.sort()

核心接口层次结构

理解 Collection 框架的最佳方式是看它的类图结构。

  • Collection (根接口): 这是最顶层的集合接口,但它本身不直接提供任何具体的实现,它定义了所有集合共有的基本方法,如 add(), remove(), size(), iterator() 等。
  • List (有序列表):
    • 特点:元素有序(按插入顺序),且允许重复
    • 常用方法get(int index), set(int index, E element), add(int index, E element)
    • 主要实现类
      • ArrayList:基于动态数组实现,查询快(O(1)),增删慢(需要移动元素),是 List 最常用的实现。
      • LinkedList:基于双向链表实现,增删快(只需修改指针),查询慢(需要遍历),额外实现了 Deque 接口,可以作为队列使用。
  • Set (无序集):
    • 特点:元素无序(具体顺序取决于实现),且不允许重复
    • 主要实现类
      • HashSet:基于 HashMap 实现,它不保证元素的顺序,性能很高(O(1)),最常用的 Set 实现。
      • LinkedHashSetHashSet 的子类,在 HashSet 的基础上维护了一个双向链表,用于记录元素的插入顺序,它既能保证唯一性,又能保证元素的插入顺序。
      • TreeSet:基于 TreeMap(红黑树)实现,元素会按照自然顺序自定义的比较器进行排序,查询、插入、删除的时间复杂度为 O(log n)。
  • Queue (队列):
    • 特点:遵循先进先出 的原则。
    • 主要实现类
      • LinkedList: 已实现 Deque 接口,可以作为双端队列使用。
      • PriorityQueue (优先队列): 元素不是按 FIFO 顺序,而是按其自然顺序自定义的比较器排序,每次取出的都是“优先级”最高的元素。
  • Map (映射):
    • 注意Map 不是 Collection 的子接口,但它通常被归为集合框架的一部分。
    • 特点:存储的是键值对键 不允许重复,值可以重复,每个键最多映射一个值。
    • 主要实现类
      • HashMap: 基于哈希表实现,查询、插入、删除的平均时间复杂度为 O(1),不保证键值对的顺序,最常用的 Map 实现。
      • LinkedHashMap: HashMap 的子类,在 HashMap 的基础上维护了一个双向链表,用于记录键值对的插入顺序或访问顺序。
      • TreeMap: 基于红黑树实现,键会按照自然顺序自定义的比较器进行排序,时间复杂度为 O(log n)。
      • Hashtable: 一个遗留类,与 HashMap 类似,但它是线程安全的,不推荐在新代码中使用,因为 ConcurrentHashMap 是更好的线程安全选择。

如何选择合适的集合?

这是一个非常实际的问题,选择哪个集合,取决于你的业务需求。

Java的Collection框架如何高效使用?-图2
(图片来源网络,侵删)
场景 推荐集合 理由
需要存储一组对象,且顺序很重要,允许重复 ArrayList 数组实现,查询快,是 List 的默认选择。
需要频繁在中间插入/删除元素 LinkedList 链表实现,增删快,但查询慢。
只需要存储一组不重复的对象,不关心顺序 HashSet 哈希实现,性能最高,是 Set 的默认选择。
需要存储一组不重复的对象,且需要保持插入顺序 LinkedHashSet HashSet 基础上维护了插入顺序。
需要存储一组不重复的对象,且需要按自然/自定义顺序排序 TreeSet 红黑树实现,自动排序。
需要存储键值对,键唯一,不关心顺序 HashMap 哈希实现,性能最高,是 Map 的默认选择。
需要存储键值对,键唯一,且需要保持插入顺序 LinkedHashMap HashMap 基础上维护了插入顺序。
需要存储键值对,键唯一,且需要按键排序 TreeMap 红黑树实现,自动按键排序。
需要先进先出的队列 LinkedListArrayDeque LinkedList 实现了 DequeArrayDeque 是基于数组的双端队列,性能更好。
需要按优先级出队的队列 PriorityQueue 内部自动排序,每次取出优先级最高的元素。

常用代码示例

List 示例 (ArrayList)

import java.util.ArrayList;
import java.util.List;
public class ListExample {
    public static void main(String[] args) {
        // 创建一个 ArrayList
        List<String> fruits = new ArrayList<>();
        // 添加元素
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");
        fruits.add("Apple"); // 允许重复
        System.out.println("List: " + fruits); // 输出: [Apple, Banana, Orange, Apple]
        // 获取元素
        String firstFruit = fruits.get(0);
        System.out.println("First fruit: " + firstFruit); // 输出: Apple
        // 在指定位置插入元素
        fruits.add(1, "Mango");
        System.out.println("After adding Mango: " + fruits); // 输出: [Apple, Mango, Banana, Orange, Apple]
        // 删除元素
        fruits.remove("Orange");
        System.out.println("After removing Orange: " + fruits); // 输出: [Apple, Mango, Banana, Apple]
        // 遍历 List (三种方式)
        System.out.println("--- Traversing List ---");
        // 方式1: for-each 循环 (最常用)
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        // 方式2: 使用 Iterator
        // fruits.iterator()...
        // 方式3: 使用 Java 8 Stream
        // fruits.stream().forEach(System.out::println);
    }
}

Set 示例 (HashSet)

import java.util.HashSet;
import java.util.Set;
public class SetExample {
    public static void main(String[] args) {
        // 创建一个 HashSet
        Set<String> fruits = new HashSet<>();
        // 添加元素
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");
        fruits.add("Apple"); // 重复元素不会被添加
        System.out.println("Set: " + fruits); // 输出顺序可能与添加顺序不同, [Orange, Apple, Banana]
        // 检查元素是否存在
        if (fruits.contains("Apple")) {
            System.out.println("Apple is in the set.");
        }
        // 删除元素
        fruits.remove("Banana");
        System.out.println("After removing Banana: " + fruits); // 输出: [Orange, Apple]
    }
}

Map 示例 (HashMap)

import java.util.HashMap;
import java.util.Map;
public class MapExample {
    public static void main(String[] args) {
        // 创建一个 HashMap
        Map<String, Integer> fruitPrices = new HashMap<>();
        // 添加键值对
        fruitPrices.put("Apple", 10);
        fruitPrices.put("Banana", 5);
        fruitPrices.put("Orange", 8);
        System.out.println("Map: " + fruitPrices); // 输出顺序可能与添加顺序不同
        // 获取值
        int applePrice = fruitPrices.get("Apple");
        System.out.println("Price of Apple: " + applePrice); // 输出: 10
        // 检查键是否存在
        if (fruitPrices.containsKey("Orange")) {
            System.out.println("Orange is in the map.");
        }
        // 遍历 Map (三种方式)
        System.out.println("--- Traversing Map ---");
        // 方式1: 遍历键
        for (String fruit : fruitPrices.keySet()) {
            System.out.println("Fruit: " + fruit);
        }
        // 方式2: 遍历值
        for (Integer price : fruitPrices.values()) {
            System.out.println("Price: " + price);
        }
        // 方式3: 遍历键值对 (最常用)
        for (Map.Entry<String, Integer> entry : fruitPrices.entrySet()) {
            System.out.println("Fruit: " + entry.getKey() + ", Price: " + entry.getValue());
        }
    }
}

线程安全与并发

标准的 Collection 实现类(如 ArrayList, HashMap不是线程安全的,如果在多线程环境下并发修改它们,可能会导致数据不一致或 ConcurrentModificationException

解决方案:

  1. Collections.synchronizedXxx():

    • 这是一个包装器,可以将非线程安全的集合转换为线程安全的。
    • List<String> list = Collections.synchronizedList(new ArrayList<>());
    • 缺点:性能较差,因为它对整个方法进行同步。
  2. CopyOnWriteArrayList / CopyOnWriteArraySet:

    • 适用于读多写少的场景。
    • 每次修改操作(add, set等)都会复制整个底层数组,所以写操作很慢,但读操作不加锁,很快。
    • List<String> list = new CopyOnWriteArrayList<>();
  3. ConcurrentHashMap:

    • 高性能的线程安全 Map 实现,它使用分段锁(Java 8后优化为CAS+synchronized)来提高并发性能。
    • 是多线程环境下 HashMap 的首选替代品。
  4. ConcurrentLinkedQueue:

    • 高性能的线程安全 Queue 实现。

Java 8+ 的新特性

Java 8 为集合框架带来了革命性的变化,主要是引入了 Stream API

Stream (流):

  • 不是数据结构,它是对集合进行操作的管道
  • 特点链式调用惰性求值(只有终端操作才会执行)、函数式编程

示例:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        // 1. 过滤出偶数
        List<Integer> evenNumbers = numbers.stream()
                                           .filter(n -> n % 2 == 0)
                                           .collect(Collectors.toList());
        System.out.println("Even numbers: " + evenNumbers); // 输出: [2, 4, 6, 8, 10]
        // 2. 将每个数字平方
        List<Integer> squaredNumbers = numbers.stream()
                                              .map(n -> n * n)
                                              .collect(Collectors.toList());
        System.out.println("Squared numbers: " + squaredNumbers); // 输出: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
        // 3. 计算所有偶数的和
        int sumOfEvens = numbers.stream()
                                .filter(n -> n % 2 == 0)
                                .mapToInt(Integer::intValue) // 转为 IntStream 以使用 sum()
                                .sum();
        System.out.println("Sum of even numbers: " + sumOfEvens); // 输出: 30
    }
}

Stream API 让集合操作变得更加简洁、易读和强大。


接口 核心特点 常用实现类 适用场景
List 有序,可重复 ArrayList, LinkedList 需要按顺序存储和访问元素,如任务列表、学生名单。
Set 无序,不重复 HashSet, LinkedHashSet, TreeSet 需要存储唯一值,如用户ID、标签、单词列表。
Map 键值对,键唯一 HashMap, LinkedHashMap, TreeMap 需要通过键来快速查找值,如字典、缓存、配置信息。
Queue FIFO LinkedList, PriorityQueue 任务调度、消息队列。

掌握 Java Collection 框架是成为合格 Java 开发者的必经之路,关键在于理解每种集合的底层原理和适用场景,并在编码中做出正确的选择。

分享:
扫描分享到社交APP
上一篇
下一篇