背景

本文是《Java 后端从小白到大神》修仙系列第五篇,正式进入Java后端世界,本篇文章主要聊Java基础中的数据结构。若想详细学习请点击首篇博文,我们开始吧。

章节概览

  1. 集合(Collection)
  2. 队列/双端队列(Queue/Deque)
  3. 数据结构选择指南

1. 集合(Collection)

集合是Java中用于存储和管理对象的容器,是Java Collections Framework的核心组件。

1.1 集合框架概述

Java Collections Framework (JCF) 是Java提供的一套用于存储和操作对象的统一架构,包含以下核心组件:

  • 接口:定义集合的行为规范(如 Collection, List, Set, Map
  • 实现类:提供具体实现(如 ArrayList, LinkedList, HashSet, HashMap
  • 工具类:提供便捷操作(如 Collections, Arrays

集合框架的层次结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Collection
├── List (有序,可重复)
│   ├── ArrayList (基于数组)
│   ├── LinkedList (基于链表)
│   └── Vector (线程安全)
├── Set (无序,不可重复)
│   ├── HashSet (基于哈希表)
│   ├── LinkedHashSet (基于链表+哈希表)
│   └── TreeSet (基于红黑树)
└── Queue (队列)
    ├── LinkedList (实现Deque接口)
    └── PriorityQueue (优先队列)

Map (键值对)
├── HashMap (基于哈希表)
├── LinkedHashMap (基于链表+哈希表)
├── TreeMap (基于红黑树)
└── Hashtable (线程安全)

1.1.1 核心底层数据结构:哈希表(Hash Table)

哈希表是 HashSetHashMap 等集合的底层实现,核心是数组 + 哈希函数 + 冲突解决,是实现 O(1) 平均时间复杂度查询/插入的关键结构。

1. 核心结构
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[ 哈希表底层结构(JDK8+) ]

index:   [0]   [1]   [2]   [3]   [4]   [5]   ...  ← 数组(桶 bucket)
          ↓     ↓     ↓     ↓     ↓     ↓
        null  node→  null  node→  null  node→
                     |           |           |
                     ↓           ↓           ↓
                   node→node    node        node→node→node  ← 链表(解决冲突)
                                      |
                                    root(红黑树,链表≥8时转换)
2. 核心原理
  • 数组:作为“桶”容器,每个下标对应一个存储位置。
  • 哈希函数:通过 hashCode() 计算元素的哈希值,再对数组长度取模,得到元素应存放的数组下标。
  • 哈希冲突:不同元素计算出相同下标时,通过链表(JDK8+ 链表过长时转为红黑树)将元素串联在同一下标下。
3. 关键特性
  • 查询/插入效率:平均 O(1),最坏 O(n)(冲突严重时),JDK8+ 红黑树优化后最坏为 O(log n)。
  • 无序性:元素位置由哈希值决定,不保证插入顺序。
  • 应用场景HashSet(存元素)、HashMap(存键值对)均基于此结构,是 Java 集合中最常用的高效存储方案。

1.2 List 接口

List 只是一个接口,不是真正存数据的结构。

它只规定了一组规则:

  • 元素有顺序(放进去什么顺序,取出来什么顺序)
  • 可以重复
  • 可以通过 下标(索引) 获取元素

真正存数据的,是它的实现类: ArrayList、LinkedList

1.2.1 ArrayList:基于动态数组的列表

底层结构

动态数组 → 内存里一整块连续空间
就像:一排连续的座位

优点

  • 按下标找元素极快 get(i) → O(1)
  • 遍历速度快

缺点

  • 中间插入、删除慢 → 后面所有元素都要移动
  • 满了要扩容(新开辟一块 1.5 倍大的空间,复制过去)

一句话总结

读多、改少、要快查 → 用 ArrayList

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ArrayListDemo {
    public static void main(String[] args) {

        // ===================== 创建 ArrayList =====================
        List<String> list = new ArrayList<>();

        // 添加元素
        list.add("Java");
        list.add("Python");
        list.add("C++");

        // ===================== 随机访问(最快) =====================
        String first = list.get(0); // 按下标获取,O(1) 效率极高


        // ===================== 3 种遍历方式(精简版) =====================

        // 1. 普通 for 循环:需要索引时使用
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        // 2. 增强 for 循环:最简洁、最常用
        for (String lang : list) {
            System.out.println(lang);
        }

        // 3. 迭代器:遍历过程中【安全删除元素】唯一正确方式
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String element = iterator.next();
            if ("Python".equals(element)) {
                iterator.remove(); // 安全删除
            }
        }
    }
}

1.2.2 LinkedList:基于双向链表的列表

LinkedList 是 List 接口的实现类,底层是双向链表,真正存数据的结构。元素在内存中不连续,靠“指针”互相连接。

核心特性

  • 底层结构:双向链表,元素分散存储,前后互相指向
  • 查找效率随机查找慢(不能直接跳位置,必须从头/尾遍历)
  • 增删效率头尾增删极快,不用移动其他元素
  • 中间增删:比 ArrayList 快(不用拷贝移动数组)
  • 额外能力:实现了队列/栈接口,可直接当队列、栈使用

优点

  1. 头尾添加、删除非常快(addFirst、addLast、removeFirst)
  2. 不需要扩容,用多少存多少
  3. 天然适合做队列、栈、双端队列

缺点

  1. 根据下标 get(i) 查找慢
  2. 占用内存比 ArrayList 多(要存前后指针)

最适合的场景

  1. 频繁在头部/尾部添加/删除
  2. 需要用队列、栈结构
  3. 元素数量经常变动、不怎么按下标查找

一句话总结

头尾增删多用 LinkedList,按下标查找多用 ArrayList。

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.LinkedList;

public class LinkedListDemo {
    public static void main(String[] args) {
        // 创建 LinkedList
        LinkedList<String> list = new LinkedList<>();

        // 增删(头尾操作极快)
        list.add("Java");
        list.addFirst("头部元素");  // 头部添加
        list.addLast("尾部元素");   // 尾部添加

        // 取元素(头尾最快,中间慢)
        String first = list.getFirst();
        String last = list.getLast();

        // 当作 队列 使用
        list.offer("入队");
        String out = list.poll(); // 出队

        // 当作 栈 使用
        list.push("入栈");
        String pop = list.pop(); // 出栈
    }
}

1.2.3 Vector:线程安全的列表

Vector 是 古老版本的线程安全 ArrayList,也是 List 接口的实现类。

核心特性

  • 底层结构:和 ArrayList 一样,基于动态数组
  • 线程安全:方法都加了 synchronized 锁,多线程下不会乱
  • 性能:因为要加锁解锁,比 ArrayList 慢很多
  • 扩容:满了默认扩容为原来的 2 倍(ArrayList 是 1.5 倍)

优点

  • 多线程环境下直接使用,不用自己加锁
  • 简单、稳定、JDK 自带

缺点

  • 并发性能差,高并发场景很慢
  • 基本已经被淘汰,现代开发很少用

实际应用场景

  • 老项目、遗留代码中还在使用
  • 简单多线程场景,对性能要求不高
  • 现在更推荐用 CopyOnWriteArrayList 替代

一句话总结

Vector = 线程安全但很慢的老式 ArrayList,现在基本不用。

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import java.util.Vector;

public class VectorDemo {
    public static void main(String[] args) {
        // 创建 Vector
        Vector<String> vector = new Vector<>();

        // 添加元素
        vector.add("Java");
        vector.add("Python");

        // 获取元素
        System.out.println(vector.get(0));
    }
}

1.2.4 List实现类对比

特性 ArrayList LinkedList Vector
底层实现 动态数组 双向链表 动态数组
随机访问 O(1) O(n) O(1)
中间插入/删除 O(n) O(n) O(n)
头尾插入/删除 O(n) O(1) O(n)
线程安全
内存占用 高(需存储指针)
适用场景 频繁随机访问 频繁增删 多线程环境

1.3 Set 接口(无序集合,元素唯一)

Set是无序的集合,不允许存储重复元素,适合需要保证元素唯一性的场景。

1.3.1 HashSet

核心特性

  • 底层实现:基于哈希表(HashMap)
  • 元素顺序:无序,不保证插入顺序
  • 查询效率:平均时间复杂度O(1)
  • 允许null:允许存储一个null元素

实际应用场景

  • 需要快速去重的场景
  • 不需要保持元素顺序的场景
  • 频繁查询元素是否存在的场景

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Set<String> hashSet = new HashSet<>();
// 添加元素
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Apple"); // 重复元素,不会被添加

// 检查元素是否存在
boolean contains = hashSet.contains("Apple"); // true

// 遍历
for (String fruit : hashSet) {
    System.out.println(fruit);
}

1.3.2 LinkedHashSet

核心特性

  • 底层实现:基于哈希表和双向链表
  • 元素顺序:维护插入顺序
  • 查询效率:接近O(1),略低于HashSet
  • 内存占用:高于HashSet(需维护链表结构)

实际应用场景

  • 需要去重且保持插入顺序的场景
  • 需要按插入顺序遍历的场景

代码示例

1
2
3
4
5
6
7
8
9
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Zebra");
linkedHashSet.add("Apple");
linkedHashSet.add("Monkey");

// 按插入顺序遍历:Zebra, Apple, Monkey
for (String animal : linkedHashSet) {
    System.out.println(animal);
}

1.3.3 TreeSet

核心特性

  • 底层实现:基于红黑树
  • 元素顺序:自然排序或自定义排序
  • 查询效率:时间复杂度O(log n)
  • 不允许null:不允许存储null元素

实际应用场景

  • 需要排序的场景
  • 需要范围查询的场景(如获取最小值、最大值、子范围等)

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 自然排序
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(5);
treeSet.add(1);
treeSet.add(3);

// 自动排序:1, 3, 5
for (Integer number : treeSet) {
    System.out.println(number);
}

// 获取最小值和最大值
System.out.println("Min: " + treeSet.first()); // 1
System.out.println("Max: " + treeSet.last());  // 5

// 自定义排序
TreeSet<String> customSet = new TreeSet<>(Comparator.reverseOrder());
customSet.add("B");
customSet.add("A");
customSet.add("C");
// 输出:C, B, A

1.3.4 Set实现类对比

特性 HashSet LinkedHashSet TreeSet
底层实现 哈希表 哈希表+双向链表 红黑树
元素顺序 无序 插入顺序 自然排序或自定义排序
查询效率 O(1) O(1) O(log n)
插入/删除效率 O(1) O(1) O(log n)
内存占用
允许null
适用场景 快速去重 去重+保持顺序 排序+范围查询

1.4 Map 接口(键值对集合)

Map是存储键值对的集合,通过键来访问值,键不能重复。

1.4.1 HashMap

核心特性

  • 底层实现:基于哈希表(数组+链表/红黑树)
  • 元素顺序:无序,不保证插入顺序
  • 查询效率:平均时间复杂度O(1)
  • 允许null:允许一个null键和多个null值
  • 线程安全:非线程安全

实际应用场景

  • 需要通过键快速查找值的场景
  • 不需要保持键值对顺序的场景
  • 单线程环境下的键值对存储

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Map<String, Integer> hashMap = new HashMap<>();
// 添加键值对
hashMap.put("Java", 100);
hashMap.put("Python", 95);
hashMap.put("C++", 90);

// 获取值
int javaScore = hashMap.get("Java"); // 100

// 遍历方式
// 1. 遍历键
for (String key : hashMap.keySet()) {
    System.out.println(key + ": " + hashMap.get(key));
}

// 2. 遍历键值对
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// 3. 遍历值
for (Integer value : hashMap.values()) {
    System.out.println(value);
}

1.4.2 LinkedHashMap

核心特性

  • 底层实现:基于哈希表和双向链表
  • 元素顺序:维护插入顺序或访问顺序(可配置)
  • 查询效率:接近O(1),略低于HashMap
  • 内存占用:高于HashMap(需维护链表结构)

实际应用场景

  • 需要保持插入顺序的场景
  • 需要实现LRU缓存的场景(访问顺序模式)

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 插入顺序模式
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("Z", 3);
linkedHashMap.put("A", 1);
linkedHashMap.put("M", 2);

// 按插入顺序遍历:Z, A, M
for (Map.Entry<String, Integer> entry : linkedHashMap.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// LRU缓存模式(访问顺序)
Map<String, Integer> lruCache = new LinkedHashMap<>(16, 0.75f, true);
lruCache.put("A", 1);
lruCache.put("B", 2);
lruCache.put("C", 3);

// 访问A,使其成为最近使用的元素
lruCache.get("A");

// 遍历顺序:B, C, A(最近访问的在最后)

1.4.3 TreeMap

核心特性

  • 底层实现:基于红黑树
  • 元素顺序:按键自然排序或自定义排序
  • 查询效率:时间复杂度O(log n)
  • 不允许null:不允许null键,允许null值

实际应用场景

  • 需要按键排序的场景
  • 需要范围查询的场景(如获取子Map、首尾键等)

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 自然排序
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("B", 2);
treeMap.put("A", 1);
treeMap.put("C", 3);

// 按键排序遍历:A, B, C
for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// 获取首尾键
System.out.println("First key: " + treeMap.firstKey()); // A
System.out.println("Last key: " + treeMap.lastKey());   // C

// 范围查询
SortedMap<String, Integer> subMap = treeMap.subMap("A", "C"); // 包含A,不包含C

1.4.4 Hashtable

核心特性

  • 底层实现:基于哈希表
  • 线程安全:所有方法都有synchronized修饰
  • 性能:由于同步开销,性能比HashMap差
  • 不允许null:不允许null键和null值

实际应用场景

  • 多线程环境下需要线程安全的键值对存储
  • 对性能要求不高的场景

代码示例

1
2
3
4
Hashtable<String, Integer> hashtable = new Hashtable<>();
hashtable.put("Key1", 10);
hashtable.put("Key2", 20);
System.out.println(hashtable.get("Key1")); // 10

1.4.5 Map实现类对比

特性 HashMap LinkedHashMap TreeMap Hashtable
底层实现 哈希表 哈希表+双向链表 红黑树 哈希表
元素顺序 无序 插入顺序或访问顺序 按键排序 无序
查询效率 O(1) O(1) O(log n) O(1)
插入/删除效率 O(1) O(1) O(log n) O(1)
线程安全
允许null 键可空值不可空 键值都不许空
适用场景 快速查找 保持顺序或LRU缓存 排序+范围查询 多线程环境

1.5 集合的实际应用示例

1.5.1 使用HashSet进行数据去重

场景:从用户输入中去重,确保每个元素只出现一次。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class DuplicateRemover {
    public static void main(String[] args) {
        // 模拟用户输入的重复数据
        String[] userInputs = {"apple", "banana", "apple", "orange", "banana", "grape"};
        
        // 使用HashSet去重
        Set<String> uniqueFruits = new HashSet<>(Arrays.asList(userInputs));
        
        System.out.println("去重后的水果:");
        for (String fruit : uniqueFruits) {
            System.out.println(fruit);
        }
        // 输出:apple, banana, orange, grape(顺序可能不同)
    }
}

1.5.2 使用LinkedHashMap实现LRU缓存

场景:实现一个简单的LRU(最近最少使用)缓存,当缓存容量达到上限时,移除最久未使用的元素。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache<K, V> {
    private final int capacity;
    private final Map<K, V> cache;
    
    public LRUCache(int capacity) {
        this.capacity = capacity;
        // 第三个参数为true,表示按访问顺序排序
        this.cache = new LinkedHashMap<K, V>(capacity, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                return size() > capacity;
            }
        };
    }
    
    public V get(K key) {
        return cache.get(key);
    }
    
    public void put(K key, V value) {
        cache.put(key, value);
    }
    
    public void display() {
        System.out.println("缓存内容:" + cache);
    }
    
    public static void main(String[] args) {
        LRUCache<String, Integer> cache = new LRUCache<>(3);
        
        cache.put("A", 1);
        cache.put("B", 2);
        cache.put("C", 3);
        cache.display(); // 输出:{A=1, B=2, C=3}
        
        // 访问A,使其成为最近使用的
        cache.get("A");
        cache.display(); // 输出:{B=2, C=3, A=1}
        
        // 添加新元素D,会移除最久未使用的B
        cache.put("D", 4);
        cache.display(); // 输出:{C=3, A=1, D=4}
    }
}

1.5.3 使用TreeMap实现区间查询

场景:根据分数区间查询学生信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.TreeMap;
import java.util.SortedMap;

public class StudentScore {
    public static void main(String[] args) {
        // 存储学生分数,键为分数,值为学生姓名
        TreeMap<Integer, String> scoreMap = new TreeMap<>();
        scoreMap.put(85, "Alice");
        scoreMap.put(92, "Bob");
        scoreMap.put(78, "Charlie");
        scoreMap.put(95, "David");
        scoreMap.put(88, "Eve");
        
        // 查询80-90分之间的学生
        SortedMap<Integer, String> rangeMap = scoreMap.subMap(80, 91);
        System.out.println("80-90分的学生:");
        for (Map.Entry<Integer, String> entry : rangeMap.entrySet()) {
            System.out.println(entry.getValue() + ": " + entry.getKey());
        }
        // 输出:
        // Alice: 85
        // Eve: 88
        
        // 查询最高分和最低分
        System.out.println("最高分:" + scoreMap.lastEntry().getValue() + ": " + scoreMap.lastKey());
        System.out.println("最低分:" + scoreMap.firstEntry().getValue() + ": " + scoreMap.firstKey());
    }
}

1.6 集合的常用方法

1.6.1 Stream API 操作

Java 8 引入的 Stream API 提供了丰富的集合操作方法,能以声明式的方式处理集合元素。

过滤操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class FilterExample {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        List<Integer> filteredList = list.stream().filter(i -> i > 1).collect(Collectors.toList());
        System.out.println(filteredList); // 输出:[2, 3]
    }
}

映射操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class MapExample {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        List<String> stringList = list.stream().map(i -> String.valueOf(i)).collect(Collectors.toList());
        System.out.println(stringList); // 输出:["1", "2", "3"]
    }
}

归约操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * 
规约(Reduce)的核心是:把一个集合(比如 List)里的多个元素,通过指定的规则,“合并 / 汇总” 成一个单一的值(可以是数字、字符串、对象等)
初始状态:累计值 = 初始值 0
第一步:累计值(0) + 当前元素(1) → 新累计值=1
第二步:累计值(1) + 当前元素(2) → 新累计值=3
第三步:累计值(3) + 当前元素(3) → 新累计值=6
最终返回:6
 */
import java.util.ArrayList;
import java.util.List;

public class ReduceExample {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        Integer sum = list.stream().reduce(0, (a, b) -> a + b);
        System.out.println(sum); // 输出:6
    }
}

1.6.2 Collections 工具类方法

排序操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SortExample {
    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]
    }
}

线程安全包装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SynchronizedListExample {
    public static void main(String[] args) {
        List<String> originalList = new ArrayList<>();
        List<String> synchronizedList = Collections.synchronizedList(originalList);
        // 现在synchronizedList是线程安全的
    }
}

不可变包装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class UnmodifiableListExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("apple");
        List<String> unmodifiableList = Collections.unmodifiableList(list);
        try {
            unmodifiableList.add("banana"); // 会抛出异常
        } catch (UnsupportedOperationException e) {
            System.out.println("修改不可修改的列表会抛出异常");
        }
    }
}

2. 队列/双端队列(Queue/Deque)

队列是一种先进先出(FIFO)的数据结构,双端队列(Deque)则允许在两端进行元素的插入和删除操作。

2.1 Queue接口

Queue接口定义了队列的基本操作,主要方法包括:

  • offer(E e):添加元素到队尾,成功返回true
  • poll():移除并返回队首元素,队列为空返回null
  • peek():返回队首元素但不移除,队列为空返回null
  • size():返回队列元素数量
  • isEmpty():判断队列是否为空

2.2 PriorityQueue:优先级队列

核心特性

  • 底层实现:基于优先级堆(二叉堆)
  • 元素顺序:按自然顺序或自定义Comparator排序
  • 出队顺序:优先级最高的元素先出队(默认是最小元素)
  • 时间复杂度:插入和删除操作的时间复杂度为O(log n)
  • 不允许null:不允许存储null元素

实际应用场景

  • 任务调度系统(按优先级处理任务)
  • 事件处理系统(按事件优先级处理)
  • 图算法(如Dijkstra最短路径算法)
  • 模拟系统(按事件发生时间排序)

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import java.util.Comparator;
import java.util.PriorityQueue;

public class PriorityQueueExample {
    public static void main(String[] args) {
        // 默认最小优先队列
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        minHeap.offer(5);
        minHeap.offer(1);
        minHeap.offer(3);
        System.out.println(minHeap.poll()); // 输出 1
        System.out.println(minHeap.poll()); // 输出 3
        System.out.println(minHeap.poll()); // 输出 5

        // 最大优先队列(使用自定义Comparator)
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
        maxHeap.offer(5);
        maxHeap.offer(1);
        maxHeap.offer(3);
        System.out.println(maxHeap.poll()); // 输出 5
        System.out.println(maxHeap.poll()); // 输出 3
        System.out.println(maxHeap.poll()); // 输出 1

        // 自定义对象的优先级队列
        class Task {
            private String name;
            private int priority;
            
            public Task(String name, int priority) {
                this.name = name;
                this.priority = priority;
            }
            
            public int getPriority() {
                return priority;
            }
            
            @Override
            public String toString() {
                return name + "(" + priority + ")";
            }
        }

        // 按优先级降序排列的任务队列
        PriorityQueue<Task> taskQueue = new PriorityQueue<>
            (Comparator.comparingInt(Task::getPriority).reversed());
        taskQueue.offer(new Task("Task1", 1));
        taskQueue.offer(new Task("Task2", 5));
        taskQueue.offer(new Task("Task3", 3));

        while (!taskQueue.isEmpty()) {
            System.out.println(taskQueue.poll());
        }
        // 输出:
        // Task2(5)
        // Task3(3)
        // Task1(1)
    }
}

2.3 ArrayDeque:数组双端队列

核心特性

  • 底层实现:基于循环数组
  • 操作效率:两端的插入和删除操作时间复杂度为O(1)
  • 容量管理:自动扩容,无容量限制
  • 元素类型:可存储null元素
  • 实现接口:实现了Deque接口,可作为队列、双端队列或栈使用

实际应用场景

  • 实现队列(FIFO)
  • 实现栈(LIFO)
  • 实现双端队列(两端操作)
  • 需要高效两端操作的场景

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.ArrayDeque;
import java.util.Deque;

public class ArrayDequeExample {
    public static void main(String[] args) {
        // 作为队列使用(FIFO)
        Deque<String> queue = new ArrayDeque<>();
        queue.offer("First");  // 添加到队尾
        queue.offer("Second");
        queue.offer("Third");
        System.out.println(queue.poll()); // 输出 First(队首)
        System.out.println(queue.poll()); // 输出 Second

        // 作为栈使用(LIFO)
        Deque<String> stack = new ArrayDeque<>();
        stack.push("First");   // 添加到栈顶
        stack.push("Second");
        stack.push("Third");
        System.out.println(stack.pop()); // 输出 Third(栈顶)
        System.out.println(stack.pop()); // 输出 Second

        // 作为双端队列使用
        Deque<String> deque = new ArrayDeque<>();
        deque.offerFirst("Front");    // 添加到队首
        deque.offerLast("Rear");     // 添加到队尾
        deque.offerFirst("Very Front");

        System.out.println(deque.pollFirst()); // 输出 Very Front
        deque.offerLast("Very Rear");
        System.out.println(deque.pollLast());  // 输出 Very Rear
        System.out.println(deque.pollFirst()); // 输出 Front
        System.out.println(deque.pollLast());  // 输出 Rear
    }
}

2.4 LinkedList:链表双端队列

核心特性

  • 底层实现:基于双向链表
  • 操作效率:两端的插入和删除操作时间复杂度为O(1)
  • 内存占用:比ArrayDeque更高(需要存储指针)
  • 实现接口:实现了Deque接口,可作为队列、双端队列或栈使用

实际应用场景

  • 需要频繁在两端操作的场景
  • 元素数量变化较大的场景
  • 实现队列、栈或双端队列

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.Deque;
import java.util.LinkedList;

public class LinkedListDequeExample {
    public static void main(String[] args) {
        // 作为队列使用
        Deque<String> queue = new LinkedList<>();
        queue.offer("A");
        queue.offer("B");
        queue.offer("C");
        System.out.println(queue.poll()); // 输出 A

        // 作为栈使用
        Deque<String> stack = new LinkedList<>();
        stack.push("X");
        stack.push("Y");
        stack.push("Z");
        System.out.println(stack.pop()); // 输出 Z

        // 作为双端队列使用
        Deque<String> deque = new LinkedList<>();
        deque.offerFirst("1");
        deque.offerLast("3");
        deque.offerFirst("0");
        deque.offerLast("4");
        System.out.println(deque.pollFirst()); // 输出 0
        System.out.println(deque.pollLast());  // 输出 4
    }
}

2.5 队列实现类对比

特性 PriorityQueue ArrayDeque LinkedList
底层实现 优先级堆 循环数组 双向链表
元素顺序 按优先级排序 插入顺序 插入顺序
出队顺序 优先级最高 FIFO(队列)/ LIFO(栈) FIFO(队列)/ LIFO(栈)
两端操作 仅队首操作 支持两端操作 支持两端操作
时间复杂度 插入/删除:O(log n) 两端操作:O(1) 两端操作:O(1)
内存占用
适用场景 优先级任务处理 高效队列/栈 频繁两端操作

2.6 队列的实际应用

  1. 消息队列

    • 异步处理任务
    • 系统间通信
    • 流量削峰
  2. 任务调度

    • 线程池任务队列
    • 定时任务调度
    • 优先级任务处理
  3. 广度优先搜索(BFS)

    • 图的遍历
    • 最短路径算法
    • 层次遍历
  4. 缓冲区

    • 数据采集缓冲区
    • 网络数据缓冲区
    • 生产-消费者模式

代码示例:使用队列实现广度优先搜索

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

public class BFSExample {
    public static void main(String[] args) {
        // 简单的图结构
        Map<String, List<String>> graph = new HashMap<>();
        graph.put("A", List.of("B", "C"));
        graph.put("B", List.of("D", "E"));
        graph.put("C", List.of("F"));
        graph.put("D", Collections.emptyList());
        graph.put("E", List.of("F"));
        graph.put("F", Collections.emptyList());
        
        // 调用BFS
        bfs(graph, "A"); // 输出:A B C D E F
    }
    
    // BFS遍历
    public static void bfs(Map<String, List<String>> graph, String start) {
        Set<String> visited = new HashSet<>();
        Queue<String> queue = new LinkedList<>();
        
        queue.offer(start);
        visited.add(start);
        
        while (!queue.isEmpty()) {
            String current = queue.poll();
            System.out.print(current + " ");
            
            for (String neighbor : graph.get(current)) {
                if (!visited.contains(neighbor)) {
                    queue.offer(neighbor);
                    visited.add(neighbor);
                }
            }
        }
    }
}

3. 数据结构选择指南

选择合适的数据结构是提高程序效率和可维护性的关键。以下是根据不同场景选择数据结构的指南:

3.1 根据操作类型选择

操作类型 推荐数据结构 原因
频繁随机访问 ArrayList 时间复杂度O(1)
频繁增删元素 LinkedList 两端操作时间复杂度O(1)
需要元素唯一 HashSet 快速去重,查询O(1)
需要按键查找 HashMap 键值对映射,查询O(1)
需要排序 TreeSet / TreeMap 自动排序,支持范围查询
需要优先级 PriorityQueue 按优先级处理元素
需要FIFO操作 ArrayDeque / LinkedList 高效队列操作
需要LIFO操作 ArrayDeque / LinkedList 高效栈操作

3.2 根据内存使用选择

内存限制 推荐数据结构 原因
内存紧张 数组 / ArrayList 内存占用低
内存充足 LinkedList / HashSet 功能更丰富

3.3 根据线程安全选择

并发需求 推荐数据结构 原因
单线程 所有非线程安全集合 性能更好
多线程 Vector / Hashtable / 同步包装集合 线程安全
高并发 ConcurrentHashMap / CopyOnWriteArrayList 并发性能更好

3.4 实际应用场景选择

应用场景 推荐数据结构 原因
用户列表 ArrayList 频繁访问,较少修改
黑名单 HashSet 快速判断元素是否存在
配置信息 HashMap 键值对存储,快速查找
任务调度 PriorityQueue 按优先级处理任务
消息队列 ArrayDeque 高效FIFO操作
浏览器历史 LinkedList 频繁增删操作
缓存 LinkedHashMap 支持LRU缓存策略

总结

Java提供了丰富的数据结构实现,每种数据结构都有其独特的特点和适用场景:

  1. 集合

    • List:有序,允许重复,适合需要索引访问的场景。
    • Set:无序,元素唯一,适合去重和快速查找。
    • Map:键值对存储,适合通过键快速查找值。
  2. 队列

    • PriorityQueue:按优先级处理元素。
    • ArrayDeque:高效双端队列,适合队列和栈操作。
    • LinkedList:双向链表实现,适合频繁两端操作。

选择数据结构时,应根据具体的操作需求、内存限制和并发场景来综合考虑,以达到最佳的性能和可维护性。通过合理选择和使用数据结构,可以显著提高程序的效率和质量。