Java 集合框架详解

Java 集合框架(Collection Framework)是 Java 语言中处理对象集合的核心 API,位于 java.util 包下。它提供了统一的接口规范和丰富的实现类,用于存储、操作和管理一组对象,解决了数组长度固定、类型单一等局限性。本文将从框架架构、核心接口、实现类特性到实战场景,全面解析 Java 集合框架,帮助你理解其设计思想与最佳实践。

一、集合框架整体架构:接口与层次关系

Java 集合框架的核心是接口(定义规范)和实现类(提供具体功能),按数据结构可分为两大体系:Collection(单值集合) 和Map(键值对集合)。整体层次结构如下:
java.util
├─ Collection(单值集合根接口)
│  ├─ List(有序、可重复)
│  │  ├─ ArrayList(动态数组)
│  │  ├─ LinkedList(双向链表)
│  │  └─ Vector(线程安全动态数组,已过时)
│  │     └─ Stack(栈,继承自Vector,已过时)
│  ├─ Set(无序、不可重复)
│  │  ├─ HashSet(哈希表实现)
│  │  ├─ TreeSet(红黑树实现,有序)
│  │  └─ LinkedHashSet(哈希表+链表,维持插入顺序)
│  └─ Queue(队列,FIFO)
│     ├─ LinkedList(双向链表实现的队列)
│     ├─ PriorityQueue(优先级队列,基于堆)
│     └─ Deque(双端队列)
│        └─ ArrayDeque(数组实现的双端队列)
└─ Map(键值对集合根接口)
   ├─ HashMap(哈希表实现)
   ├─ TreeMap(红黑树实现,键有序)
   ├─ LinkedHashMap(哈希表+链表,维持插入/访问顺序)
   └─ Hashtable(线程安全哈希表,已过时)
      └─ Properties(键值均为字符串的配置类)
 

核心设计思想:

  • 接口标准化:通过 CollectionListSetMap 等接口定义统一操作(如 add()remove()iterator()),实现类遵循接口规范,便于替换和扩展。
  • 数据结构封装:将数组、链表、红黑树、哈希表等数据结构封装为实现类,开发者无需关注底层细节,直接调用 API 即可。
  • 泛型支持:JDK 5+ 引入泛型(如 List<String>),解决集合元素类型不安全问题,避免运行时类型转换错误。

二、Collection 接口:单值集合的通用规范

Collection 是所有单值集合的根接口,定义了操作集合的通用方法,主要包括:
 
方法签名功能描述
boolean add(E e) 添加元素到集合
boolean remove(Object o) 从集合中移除指定元素
boolean contains(Object o) 判断集合是否包含指定元素
int size() 返回集合元素个数
boolean isEmpty() 判断集合是否为空
Iterator<E> iterator() 返回迭代器,用于遍历集合
Object[] toArray() 将集合转换为数组
 
注意:Collection 接口未提供索引访问方法(如 get(int index)),这类方法由子接口 List 定义。

三、List 接口:有序可重复的动态序列

List 接口继承自 Collection,特点是元素有序(插入顺序)、可重复,支持通过索引访问元素,是最常用的集合类型之一。

1. ArrayList:基于动态数组的 List 实现

底层实现:

  • 核心是动态扩容数组:初始容量为 10,当元素数量超过当前容量时,自动扩容为原容量的 1.5 倍(oldCapacity + (oldCapacity >> 1))。
  • 存储结构:连续内存空间,元素按索引顺序存储,类似数组但长度可动态调整。

核心特性:

  • 时间复杂度
    • 随机访问(get(int index)):O(1)(直接通过索引定位);
    • 尾部插入 / 删除(add(E e)remove(int size-1)):O(1)
    • 中间插入 / 删除(add(int index, E e)):O(n)(需移动后续元素)。
  • 优缺点
    • 优点:查询效率高,适合读多写少场景;
    • 缺点:中间插入 / 删除效率低,扩容时可能浪费内存(需复制旧数组到新数组)。

示例代码:

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        // 添加元素
        fruits.add("苹果");
        fruits.add("香蕉");
        fruits.add("橙子");
        
        // 索引访问
        System.out.println(fruits.get(1)); // 输出:香蕉
        
        // 中间插入
        fruits.add(1, "草莓"); // 结果:[苹果, 草莓, 香蕉, 橙子]
        
        // 遍历(for循环+索引)
        for (int i = 0; i < fruits.size(); i++) {
            System.out.print(fruits.get(i) + " "); // 输出:苹果 草莓 香蕉 橙子
        }
        
        // 删除元素
        fruits.remove(2); // 删除索引2的"香蕉"
        System.out.println("\n" + fruits); // 输出:[苹果, 草莓, 橙子]
    }
}
 

2. LinkedList:基于双向链表的 List 实现

底层实现:

  • 核心是双向链表:每个节点(Node)包含 prev(前驱节点引用)、item(元素值)、next(后继节点引用)。
  • 存储结构:非连续内存空间,元素通过节点引用关联,无需预先分配固定大小的内存。

核心特性:

  • 时间复杂度
    • 随机访问(get(int index)):O(n)(需从头 / 尾遍历到指定索引);
    • 头部 / 尾部插入 / 删除(addFirst(E e)removeLast()):O(1)
    • 中间插入 / 删除(已知节点位置时):O(1)(仅需修改节点引用),但定位节点仍需 O(n)
  • 优缺点
    • 优点:头尾操作效率高,内存利用率灵活(无需连续空间);
    • 缺点:查询效率低,不适合频繁随机访问。

示例代码:

import java.util.LinkedList;
import java.util.List;

public class LinkedListDemo {
    public static void main(String[] args) {
        List<Integer> numbers = new LinkedList<>();
        // 添加元素
        numbers.add(10);
        numbers.add(20);
        numbers.add(30);
        
        // 头部插入
        numbers.add(0, 5); // 结果:[5, 10, 20, 30]
        
        // 尾部删除
        numbers.remove(numbers.size() - 1); // 结果:[5, 10, 20]
        
        // 遍历(增强for循环)
        for (int num : numbers) {
            System.out.print(num + " "); // 输出:5 10 20
        }
    }
}
 

3. List 实现类对比与选择

场景需求推荐实现类理由
频繁查询,少量增删 ArrayList 随机访问效率 O(1),适合读多写少
频繁头尾增删,少量查询 LinkedList 头尾操作 O(1),适合队列 / 栈场景
多线程环境(需线程安全) CopyOnWriteArrayList 读写分离,适合读多写极少场景(JUC 包)

四、Set 接口:无序不可重复的集合

Set 接口继承自 Collection,特点是元素无序(插入顺序不保证)、不可重复,适合存储唯一值(如用户 ID、标签)。其核心是去重机制:通过元素的 hashCode() 和 equals() 方法判断是否重复(先比较哈希值,哈希值相同再比较内容)。

1. HashSet:基于哈希表的 Set 实现

底层实现:

  • 本质是HashMap 的封装HashSet 内部维护一个 HashMap,元素作为 HashMap 的 key 存储(value 为固定的空对象 PRESENT)。
  • 去重逻辑:同 HashMap 的 key 去重,依赖元素重写 hashCode() 和 equals() 方法。

核心特性:

  • 时间复杂度:添加 / 删除 / 查询均为 O(1)(哈希表理想情况,无哈希冲突);
  • 无序性:元素存储位置由哈希值决定,遍历顺序与插入顺序无关;
  • 允许 null 元素:但仅能有一个 null(因不可重复)。

示例代码:

import java.util.HashSet;
import java.util.Set;

public class HashSetDemo {
    public static void main(String[] args) {
        Set<String> cities = new HashSet<>();
        // 添加元素
        cities.add("北京");
        cities.add("上海");
        cities.add("广州");
        cities.add("北京"); // 重复元素,不添加
        
        // 遍历(无序)
        for (String city : cities) {
            System.out.print(city + " "); // 输出可能为:上海 北京 广州(顺序不固定)
        }
        
        // 判断是否包含
        System.out.println("\n是否包含深圳?" + cities.contains("深圳")); // 输出:false
    }
}
 

2. TreeSet:基于红黑树的有序 Set 实现

底层实现:

  • 本质是TreeMap 的封装:内部维护一个 TreeMap,元素作为 TreeMap 的 key 存储,依赖红黑树(自平衡二叉查找树)实现排序。
  • 排序逻辑:
    • 自然排序:元素实现 Comparable 接口,重写 compareTo() 方法;
    • 定制排序:创建 TreeSet 时传入 Comparator 比较器。

核心特性:

  • 时间复杂度:添加 / 删除 / 查询均为 O(log n)(红黑树高度为 log n);
  • 有序性:元素按排序规则升序排列(默认自然排序);
  • 不允许 null 元素:插入 null 会抛 NullPointerException

示例代码:

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetDemo {
    public static void main(String[] args) {
        // 自然排序(整数按大小排序)
        Set<Integer> nums = new TreeSet<>();
        nums.add(30);
        nums.add(10);
        nums.add(20);
        System.out.println(nums); // 输出:[10, 20, 30](自然排序)
        
        // 定制排序(字符串按长度降序)
        Set<String> words = new TreeSet<>(Comparator.comparingInt(String::length).reversed());
        words.add("apple");
        words.add("banana");
        words.add("pear");
        System.out.println(words); // 输出:[banana, apple, pear](长度:6→5→4)
    }
}
 

3. LinkedHashSet:维持插入顺序的 Set 实现

底层实现:

  • 继承自 HashSet,内部使用LinkedHashMap(哈希表 + 双向链表),通过链表记录元素插入顺序。

核心特性:

  • 兼具 HashSet 的查询效率(O(1))和 LinkedList 的顺序性(插入顺序);
  • 内存开销略高于 HashSet(需维护链表)。

适用场景:

需去重且关注插入顺序的场景(如日志记录、历史操作记录)。

4. Set 实现类对比与选择

场景需求推荐实现类理由
去重且无需排序 HashSet 效率最高,O(1) 操作复杂度
去重且需排序 TreeSet 自动按规则排序,O(log n) 效率
去重且需保留插入顺序 LinkedHashSet 平衡效率与顺序需求,内存略高

五、Queue 接口:先进先出的队列

Queue 接口继承自 Collection,定义了FIFO(先进先出) 的队列操作规范,主要用于实现队列、缓冲区等场景。

1. LinkedList:双向链表实现的队列

LinkedList 实现了 Queue 接口,可作为队列使用,核心方法:
 
方法功能失败时行为
boolean offer(E e) 入队(尾部添加) 失败返回 false
E poll() 出队(头部移除并返回) 队空返回 null
E peek() 查看队头元素 队空返回 null

示例代码:

import java.util.LinkedList;
import java.util.Queue;

public class QueueDemo {
    public static void main(String[] args) {
        Queue<String> queue = new LinkedList<>();
        // 入队
        queue.offer("任务1");
        queue.offer("任务2");
        queue.offer("任务3");
        
        // 出队(FIFO)
        while (!queue.isEmpty()) {
            System.out.println("处理:" + queue.poll()); 
            // 输出:处理:任务1 → 处理:任务2 → 处理:任务3
        }
    }
}
 

2. PriorityQueue:基于堆的优先级队列

底层实现:

  • 基于小顶堆(默认)实现,元素按优先级出队(优先级最高的元素先出队)。
  • 排序逻辑:同 TreeSet,支持自然排序或定制排序。

核心特性:

  • 出队顺序由优先级决定,而非插入顺序;
  • 时间复杂度:入队 / 出队 O(log n),查看队头 O(1)

示例代码:

import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueDemo {
    public static void main(String[] args) {
        // 优先级队列(默认小顶堆,整数从小到大出队)
        Queue<Integer> pq = new PriorityQueue<>();
        pq.offer(30);
        pq.offer(10);
        pq.offer(20);
        
        while (!pq.isEmpty()) {
            System.out.println("出队:" + pq.poll()); 
            // 输出:出队:10 → 出队:20 → 出队:30(按优先级)
        }
    }
}
 

六、Map 接口:键值对映射集合

Map 接口是独立于 Collection 的另一大体系,用于存储键值对(key-value),其中 key 唯一(不可重复),value 可重复。key 的去重逻辑同 Set(依赖 hashCode() 和 equals())。

1. HashMap:基于哈希表的 Map 实现

底层实现(JDK 1.8+):

  • 数组 + 链表 + 红黑树
    • 数组(桶):每个索引对应一个链表或红黑树;
    • 链表:当哈希冲突时,元素以链表形式存储;
    • 红黑树:当链表长度超过 8 且数组容量 ≥ 64 时,链表转为红黑树(优化查询效率)。
  • 扩容机制:初始容量 16,负载因子 0.75(当元素数 > 容量 × 负载因子时,扩容为原容量的 2 倍)。

核心特性:

  • 时间复杂度:理想情况 O(1)(哈希表无冲突),红黑树场景 O(log n)
  • 无序性key 的存储顺序与插入顺序无关;
  • 允许 nullkey 最多一个 nullvalue 可多个 null
  • 线程不安全:多线程并发修改可能导致死循环(JDK 1.7)或数据异常。

示例代码:

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

public class HashMapDemo {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        // 添加键值对
        scores.put("张三", 90);
        scores.put("李四", 85);
        scores.put("王五", 95);
        
        // 获取值
        System.out.println("李四的成绩:" + scores.get("李四")); // 输出:85
        
        // 遍历(entrySet() 效率高于 keySet())
        for (Map.Entry<String, Integer> entry : scores.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        
        // 判断key是否存在
        System.out.println("是否有赵六?" + scores.containsKey("赵六")); // 输出:false
    }
}
 

2. TreeMap:基于红黑树的有序 Map 实现

底层实现:

  • 基于红黑树key 按排序规则(自然排序或定制排序)存储,遍历顺序为升序。

核心特性:

  • 时间复杂度:添加 / 删除 / 查询均为 O(log n)
  • 有序性key 按排序规则排列,支持范围查询(如 subMap()headMap());
  • 不允许 null keykey 为 null 会抛 NullPointerException

示例代码:

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

public class TreeMapDemo {
    public static void main(String[] args) {
        // 自然排序(字符串按字典序)
        Map<String, String> dict = new TreeMap<>();
        dict.put("banana", "香蕉");
        dict.put("apple", "苹果");
        dict.put("cherry", "樱桃");
        
        // 遍历(按key升序)
        for (Map.Entry<String, String> entry : dict.entrySet()) {
            System.out.println(entry.getKey() + " → " + entry.getValue());
            // 输出:apple → 苹果 → banana → 香蕉 → cherry → 樱桃
        }
    }
}
 

3. LinkedHashMap:维持顺序的 Map 实现

底层实现:

  • 继承自 HashMap,内部通过双向链表记录 key 的顺序,支持两种顺序:
    • 插入顺序(默认):遍历顺序与插入顺序一致;
    • 访问顺序(accessOrder=true):每次访问(get())元素后,该元素移至链表尾部,适合实现 LRU 缓存。

示例代码(LRU 缓存思想):

import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        // 访问顺序模式(accessOrder=true),初始容量16,负载因子0.75
        Map<String, Integer> lruCache = new LinkedHashMap<>(16, 0.75f, true) {
            // 重写此方法,当元素数超过3时,自动删除最久未访问的元素
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, Integer> eldest) {
                return size() > 3;
            }
        };
        
        lruCache.put("A", 1);
        lruCache.put("B", 2);
        lruCache.put("C", 3);
        System.out.println(lruCache.keySet()); // 输出:[A, B, C](插入顺序)
        
        lruCache.get("A"); // 访问A,移至尾部
        System.out.println(lruCache.keySet()); // 输出:[B, C, A]
        
        lruCache.put("D", 4); // 超过容量3,删除最久未访问的B
        System.out.println(lruCache.keySet()); // 输出:[C, A, D]
    }
}
 

4. Map 实现类对比与选择

场景需求推荐实现类理由
键值对存储,无序 HashMap 效率最高,适合多数场景
键值对存储,需按 key 排序 TreeMap 支持范围查询,O(log n) 效率
键值对存储,需保留插入 / 访问顺序 LinkedHashMap 可实现 LRU 缓存,内存略高
多线程环境(需线程安全) ConcurrentHashMap 分段锁 / CAS 实现,高并发下性能优于 Hashtable(JUC 包)

七、集合框架的关键工具类

1. Collections:集合工具类

提供静态方法操作集合,如排序、查找、同步包装等:
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(3);
        list.add(1);
        list.add(2);
        
        // 排序(自然排序)
        Collections.sort(list);
        System.out.println(list); // 输出:[1, 2, 3]
        
        // 反转
        Collections.reverse(list);
        System.out.println(list); // 输出:[3, 2, 1]
        
        // 创建线程安全的List(不推荐,效率低,建议用JUC包的并发集合)
        List<Integer> syncList = Collections.synchronizedList(list);
    }
}
 

2. Arrays:数组工具类

提供数组与集合的转换方法(如 asList()):
 
import java.util.Arrays;
import java.util.List;

public class ArraysDemo {
    public static void main(String[] args) {
        // 数组转List(返回的是固定大小的List,不可添加/删除元素)
        List<String> list = Arrays.asList("a", "b", "c");
        System.out.println(list); // 输出:[a, b, c]
        
        // 若需可修改的List,需包装为ArrayList
        List<String> mutableList = new ArrayList<>(Arrays.asList("a", "b", "c"));
        mutableList.add("d"); // 可行
    }
}
 

八、集合框架的线程安全性

Java 集合框架中,ArrayListHashMapHashSet 等均为线程不安全(多线程并发修改可能导致数据异常),线程安全的集合解决方案:
 
  1. 使用同步包装类Collections.synchronizedList()synchronizedMap() 等,通过加锁实现线程安全,但性能低(全局锁)。
  2. 使用 JUC 并发集合java.util.concurrent 包下的集合(如 CopyOnWriteArrayListConcurrentHashMap),采用更高效的并发策略(如读写分离、分段锁),适合高并发场景。
  3. 手动同步:在多线程操作时,通过 synchronized 或 Lock 手动加锁,灵活但需注意锁粒度。

九、总结:集合框架的选择指南

Java 集合框架的核心是 “根据场景选结构”,选择时需关注:
 
  • 操作类型:查询多→ArrayList/HashMap;增删多(头尾)→LinkedList;需排序→TreeSet/TreeMap
  • 顺序需求:需插入顺序→LinkedList/LinkedHashSet/LinkedHashMap;需排序→Tree 系列。
  • 线程安全:单线程→普通集合;多线程→JUC 并发集合(如 ConcurrentHashMap)。
  • 性能考量:哈希表(Hash 系列)平均 O(1) 效率最高;红黑树(Tree 系列)O(log n) 适合排序场景。

posted on 2025-10-13 09:59  coding博客  阅读(56)  评论(0)    收藏  举报