【Java集合框架深度解析:从入门到精通-后端技术栈】

Java集合框架是Java编程的基石,掌握集合的使用和原理是每个Java开发者的必备技能。本文将深入剖析Java集合框架的核心接口和常用实现类,助你快速掌握集合的使用技巧。

一、集合框架概述

1.1 什么是集合?

关键点: 集合是用于存储和操作一组对象的容器,提供了比数组更灵活的数据结构。

数组 vs 集合:

java 复制代码
// ❌ 数组的局限性
public class ArrayDemo {
    public static void main(String[] args) {
        // 1. 长度固定,不能动态扩展
        String[] names = new String[3];
        names[0] = "张三";
        names[1] = "李四";
        names[2] = "王五";
        // names[3] = "赵六";  // 数组越界!
        
        // 2. 只能存储同一类型
        // String[] mixed = {"张三", 123};  // 编译错误
        
        // 3. 没有提供便捷的操作方法
        // 查找元素需要手动遍历
        boolean found = false;
        for (String name : names) {
            if ("李四".equals(name)) {
                found = true;
                break;
            }
        }
    }
}

// ✅ 集合的优势
public class CollectionDemo {
    public static void main(String[] args) {
        // 1. 长度可变,自动扩容
        List<String> names = new ArrayList<>();
        names.add("张三");
        names.add("李四");
        names.add("王五");
        names.add("赵六");  // 自动扩容
        
        // 2. 提供丰富的操作方法
        boolean contains = names.contains("李四");  // 直接查找
        names.remove("王五");  // 直接删除
        int size = names.size();  // 获取大小
        
        // 3. 支持泛型,类型安全
        List<Integer> numbers = new ArrayList<>();
        numbers.add(100);
        // numbers.add("abc");  // 编译错误,类型不匹配
    }
}

1.2 集合框架体系

复制代码
Collection (接口)
├── List (接口) - 有序、可重复
│   ├── ArrayList - 动态数组
│   ├── LinkedList - 双向链表
│   └── Vector - 线程安全的动态数组
│
├── Set (接口) - 无序、不可重复
│   ├── HashSet - 基于HashMap
│   ├── LinkedHashSet - 保持插入顺序
│   └── TreeSet - 有序集合(红黑树)
│
└── Queue (接口) - 队列
    ├── PriorityQueue - 优先队列
    └── Deque - 双端队列
        └── ArrayDeque

Map (接口) - 键值对
├── HashMap - 哈希表
├── LinkedHashMap - 保持插入顺序
├── TreeMap - 有序Map(红黑树)
└── Hashtable - 线程安全(已过时)

二、List接口详解

2.1 ArrayList - 动态数组

关键点: ArrayList基于数组实现,查询快,增删慢。

java 复制代码
public class ArrayListDemo {
    public static void main(String[] args) {
        // 创建ArrayList
        List<String> list = new ArrayList<>();
        
        // 添加元素
        list.add("Java");
        list.add("Python");
        list.add("C++");
        list.add(1, "JavaScript");  // 在指定位置插入
        
        System.out.println(list);  // [Java, JavaScript, Python, C++]
        
        // 获取元素
        String first = list.get(0);  // Java
        
        // 修改元素
        list.set(1, "TypeScript");
        
        // 删除元素
        list.remove(2);  // 删除索引2的元素
        list.remove("C++");  // 删除指定元素
        
        // 查找元素
        int index = list.indexOf("Java");  // 0
        boolean contains = list.contains("Python");  // false
        
        // 遍历
        for (String lang : list) {
            System.out.println(lang);
        }
        
        // 使用Lambda表达式遍历
        list.forEach(lang -> System.out.println(lang));
        
        // 使用Stream API
        list.stream()
            .filter(lang -> lang.startsWith("J"))
            .forEach(System.out::println);
    }
}

ArrayList特点:

  • ✅ 查询快:O(1)时间复杂度
  • ❌ 插入删除慢:需要移动元素
  • ✅ 支持随机访问
  • ❌ 非线程安全

2.2 LinkedList - 双向链表

关键点: LinkedList基于链表实现,增删快,查询慢。

java 复制代码
public class LinkedListDemo {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>();
        
        // 添加元素
        list.add("A");
        list.add("B");
        list.add("C");
        
        // 头部操作
        list.addFirst("头部");
        list.addLast("尾部");
        
        // 获取元素
        String first = list.getFirst();  // 头部
        String last = list.getLast();    // 尾部
        
        // 删除元素
        list.removeFirst();
        list.removeLast();
        
        // 作为队列使用
        list.offer("D");  // 入队
        String element = list.poll();  // 出队
        
        // 作为栈使用
        list.push("E");  // 入栈
        String top = list.pop();  // 出栈
        
        System.out.println(list);
    }
}

ArrayList vs LinkedList:

特性 ArrayList LinkedList
底层结构 动态数组 双向链表
查询速度 快 O(1) 慢 O(n)
插入删除 慢 O(n) 快 O(1)
内存占用 连续内存 不连续,额外指针
使用场景 查询多 增删多

三、Set接口详解

3.1 HashSet - 无序不重复

关键点: HashSet基于HashMap实现,元素无序且不重复。

java 复制代码
public class HashSetDemo {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        
        // 添加元素
        set.add("Apple");
        set.add("Banana");
        set.add("Orange");
        set.add("Apple");  // 重复元素不会添加
        
        System.out.println(set);  // [Apple, Orange, Banana] 顺序不确定
        System.out.println("大小:" + set.size());  // 3
        
        // 判断是否包含
        boolean contains = set.contains("Apple");  // true
        
        // 删除元素
        set.remove("Banana");
        
        // 遍历
        for (String fruit : set) {
            System.out.println(fruit);
        }
        
        // 集合运算
        Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
        Set<Integer> set2 = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));
        
        // 并集
        Set<Integer> union = new HashSet<>(set1);
        union.addAll(set2);
        System.out.println("并集:" + union);  // [1, 2, 3, 4, 5, 6, 7, 8]
        
        // 交集
        Set<Integer> intersection = new HashSet<>(set1);
        intersection.retainAll(set2);
        System.out.println("交集:" + intersection);  // [4, 5]
        
        // 差集
        Set<Integer> difference = new HashSet<>(set1);
        difference.removeAll(set2);
        System.out.println("差集:" + difference);  // [1, 2, 3]
    }
}

3.2 TreeSet - 有序集合

关键点: TreeSet基于红黑树实现,元素自动排序。

java 复制代码
public class TreeSetDemo {
    public static void main(String[] args) {
        // 自然排序
        TreeSet<Integer> numbers = new TreeSet<>();
        numbers.add(5);
        numbers.add(2);
        numbers.add(8);
        numbers.add(1);
        
        System.out.println(numbers);  // [1, 2, 5, 8] 自动排序
        
        // 获取最小值和最大值
        System.out.println("最小值:" + numbers.first());  // 1
        System.out.println("最大值:" + numbers.last());   // 8
        
        // 范围查询
        System.out.println("小于5的元素:" + numbers.headSet(5));  // [1, 2]
        System.out.println("大于等于5的元素:" + numbers.tailSet(5));  // [5, 8]
        System.out.println("2到8之间:" + numbers.subSet(2, 8));  // [2, 5]
        
        // 自定义排序
        TreeSet<String> names = new TreeSet<>((a, b) -> b.compareTo(a));  // 降序
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        
        System.out.println(names);  // [Charlie, Bob, Alice]
    }
}

// 自定义对象排序
class Student implements Comparable<Student> {
    private String name;
    private int score;
    
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    
    @Override
    public int compareTo(Student other) {
        // 按分数降序排列
        return other.score - this.score;
    }
    
    @Override
    public String toString() {
        return name + ":" + score;
    }
}

public class CustomSortDemo {
    public static void main(String[] args) {
        TreeSet<Student> students = new TreeSet<>();
        students.add(new Student("张三", 85));
        students.add(new Student("李四", 92));
        students.add(new Student("王五", 78));
        
        System.out.println(students);  // [李四:92, 张三:85, 王五:78]
    }
}

四、Map接口详解

4.1 HashMap - 键值对存储

关键点: HashMap基于哈希表实现,键不能重复,值可以重复。

java 复制代码
public class HashMapDemo {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        
        // 添加键值对
        map.put("张三", 85);
        map.put("李四", 92);
        map.put("王五", 78);
        map.put("张三", 90);  // 键重复,会覆盖旧值
        
        System.out.println(map);  // {李四=92, 张三=90, 王五=78}
        
        // 获取值
        Integer score = map.get("张三");  // 90
        Integer defaultScore = map.getOrDefault("赵六", 0);  // 0
        
        // 判断是否包含
        boolean hasKey = map.containsKey("李四");  // true
        boolean hasValue = map.containsValue(92);  // true
        
        // 删除
        map.remove("王五");
        
        // 遍历方式1:遍历键
        for (String name : map.keySet()) {
            System.out.println(name + ": " + map.get(name));
        }
        
        // 遍历方式2:遍历值
        for (Integer value : map.values()) {
            System.out.println(value);
        }
        
        // 遍历方式3:遍历键值对(推荐)
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        
        // 遍历方式4:Lambda表达式(推荐)
        map.forEach((name, score1) -> {
            System.out.println(name + ": " + score1);
        });
        
        // Java 8新方法
        map.putIfAbsent("赵六", 88);  // 键不存在时才添加
        map.computeIfAbsent("孙七", k -> 95);  // 键不存在时计算并添加
        map.merge("张三", 5, Integer::sum);  // 合并值
    }
}

4.2 HashMap原理解析

java 复制代码
/**
 * HashMap底层原理:
 * 1. 数组 + 链表 + 红黑树(JDK 1.8+)
 * 2. 默认容量16,负载因子0.75
 * 3. 当链表长度>8且数组长度>=64时,转为红黑树
 */

public class HashMapPrincipleDemo {
    public static void main(String[] args) {
        // 指定初始容量和负载因子
        Map<String, String> map = new HashMap<>(32, 0.8f);
        
        // 哈希冲突演示
        Map<Integer, String> conflictMap = new HashMap<>();
        
        // 这些键的hashCode可能产生冲突
        for (int i = 0; i < 20; i++) {
            conflictMap.put(i, "Value" + i);
        }
        
        System.out.println("大小:" + conflictMap.size());
    }
}

// 自定义对象作为Key
class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 必须重写hashCode和equals
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }
    
    @Override
    public String toString() {
        return name + "(" + age + ")";
    }
}

public class CustomKeyDemo {
    public static void main(String[] args) {
        Map<Person, String> map = new HashMap<>();
        
        Person p1 = new Person("张三", 25);
        Person p2 = new Person("张三", 25);  // 内容相同
        
        map.put(p1, "员工1");
        map.put(p2, "员工2");  // 会覆盖p1,因为equals返回true
        
        System.out.println(map.size());  // 1
        System.out.println(map.get(p1));  // 员工2
    }
}

4.3 TreeMap - 有序Map

关键点: TreeMap基于红黑树实现,键自动排序。

java 复制代码
public class TreeMapDemo {
    public static void main(String[] args) {
        // 自然排序
        TreeMap<Integer, String> map = new TreeMap<>();
        map.put(3, "C");
        map.put(1, "A");
        map.put(2, "B");
        
        System.out.println(map);  // {1=A, 2=B, 3=C} 按键排序
        
        // 获取第一个和最后一个
        System.out.println("第一个:" + map.firstEntry());  // 1=A
        System.out.println("最后一个:" + map.lastEntry());  // 3=C
        
        // 范围查询
        System.out.println("小于3的:" + map.headMap(3));  // {1=A, 2=B}
        System.out.println("大于等于2的:" + map.tailMap(2));  // {2=B, 3=C}
        
        // 自定义排序
        TreeMap<String, Integer> scoreMap = new TreeMap<>((a, b) -> b.compareTo(a));
        scoreMap.put("张三", 85);
        scoreMap.put("李四", 92);
        scoreMap.put("王五", 78);
        
        System.out.println(scoreMap);  // {王五=78, 李四=92, 张三=85} 键降序
    }
}

五、实战案例

5.1 统计单词频率

java 复制代码
public class WordFrequency {
    public static void main(String[] args) {
        String text = "Java is great and Java is powerful Java is everywhere";
        
        // 分割单词
        String[] words = text.toLowerCase().split("\\s+");
        
        // 使用HashMap统计频率
        Map<String, Integer> frequency = new HashMap<>();
        
        for (String word : words) {
            frequency.put(word, frequency.getOrDefault(word, 0) + 1);
        }
        
        // 按频率排序
        List<Map.Entry<String, Integer>> list = new ArrayList<>(frequency.entrySet());
        list.sort((a, b) -> b.getValue() - a.getValue());
        
        // 输出结果
        System.out.println("单词频率统计:");
        for (Map.Entry<String, Integer> entry : list) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

5.2 学生成绩管理系统

java 复制代码
class Student {
    private String id;
    private String name;
    private Map<String, Integer> scores;  // 科目-成绩
    
    public Student(String id, String name) {
        this.id = id;
        this.name = name;
        this.scores = new HashMap<>();
    }
    
    public void addScore(String subject, int score) {
        scores.put(subject, score);
    }
    
    public double getAverage() {
        if (scores.isEmpty()) return 0;
        return scores.values().stream()
                    .mapToInt(Integer::intValue)
                    .average()
                    .orElse(0.0);
    }
    
    public void displayInfo() {
        System.out.println("学号:" + id + ",姓名:" + name);
        System.out.println("成绩:" + scores);
        System.out.println("平均分:" + String.format("%.2f", getAverage()));
    }
    
    // Getters
    public String getId() { return id; }
    public String getName() { return name; }
    public Map<String, Integer> getScores() { return scores; }
}

class ScoreManager {
    private Map<String, Student> students;  // 学号-学生
    
    public ScoreManager() {
        this.students = new HashMap<>();
    }
    
    // 添加学生
    public void addStudent(Student student) {
        students.put(student.getId(), student);
        System.out.println("添加学生成功:" + student.getName());
    }
    
    // 录入成绩
    public void addScore(String studentId, String subject, int score) {
        Student student = students.get(studentId);
        if (student != null) {
            student.addScore(subject, score);
            System.out.println("录入成绩成功");
        } else {
            System.out.println("学生不存在");
        }
    }
    
    // 查询学生信息
    public void queryStudent(String studentId) {
        Student student = students.get(studentId);
        if (student != null) {
            student.displayInfo();
        } else {
            System.out.println("学生不存在");
        }
    }
    
    // 统计某科目平均分
    public double getSubjectAverage(String subject) {
        return students.values().stream()
                .filter(s -> s.getScores().containsKey(subject))
                .mapToInt(s -> s.getScores().get(subject))
                .average()
                .orElse(0.0);
    }
    
    // 获取成绩排名
    public List<Student> getRanking() {
        List<Student> ranking = new ArrayList<>(students.values());
        ranking.sort((a, b) -> Double.compare(b.getAverage(), a.getAverage()));
        return ranking;
    }
    
    // 显示所有学生
    public void displayAllStudents() {
        System.out.println("\n===== 所有学生 =====");
        students.values().forEach(Student::displayInfo);
    }
}

public class ScoreManagementSystem {
    public static void main(String[] args) {
        ScoreManager manager = new ScoreManager();
        
        // 添加学生
        manager.addStudent(new Student("2021001", "张三"));
        manager.addStudent(new Student("2021002", "李四"));
        manager.addStudent(new Student("2021003", "王五"));
        
        // 录入成绩
        manager.addScore("2021001", "数学", 85);
        manager.addScore("2021001", "英语", 90);
        manager.addScore("2021001", "语文", 88);
        
        manager.addScore("2021002", "数学", 92);
        manager.addScore("2021002", "英语", 87);
        manager.addScore("2021002", "语文", 95);
        
        manager.addScore("2021003", "数学", 78);
        manager.addScore("2021003", "英语", 82);
        manager.addScore("2021003", "语文", 80);
        
        // 查询学生
        manager.queryStudent("2021001");
        
        // 统计科目平均分
        System.out.println("\n数学平均分:" + 
            String.format("%.2f", manager.getSubjectAverage("数学")));
        
        // 显示排名
        System.out.println("\n===== 成绩排名 =====");
        List<Student> ranking = manager.getRanking();
        for (int i = 0; i < ranking.size(); i++) {
            Student s = ranking.get(i);
            System.out.println("第" + (i + 1) + "名:" + s.getName() + 
                " - 平均分:" + String.format("%.2f", s.getAverage()));
        }
    }
}

5.3 去重和排序

java 复制代码
public class DeduplicationAndSort {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 2, 8, 2, 9, 1, 5, 3);
        
        // 方法1:使用HashSet去重
        Set<Integer> uniqueSet = new HashSet<>(numbers);
        List<Integer> result1 = new ArrayList<>(uniqueSet);
        Collections.sort(result1);
        System.out.println("方法1:" + result1);
        
        // 方法2:使用TreeSet去重并排序
        Set<Integer> sortedSet = new TreeSet<>(numbers);
        System.out.println("方法2:" + sortedSet);
        
        // 方法3:使用Stream API
        List<Integer> result3 = numbers.stream()
                .distinct()
                .sorted()
                .collect(Collectors.toList());
        System.out.println("方法3:" + result3);
    }
}

六、集合选择指南

6.1 如何选择合适的集合?

选择List:

  • 需要保持元素顺序
  • 允许重复元素
  • 需要通过索引访问

选择Set:

  • 不允许重复元素
  • 不关心元素顺序(HashSet)
  • 需要自动排序(TreeSet)

选择Map:

  • 需要键值对存储
  • 通过键快速查找值
  • 需要统计、分组等操作

6.2 性能对比

操作 ArrayList LinkedList HashSet TreeSet HashMap TreeMap
添加 O(1) O(1) O(1) O(log n) O(1) O(log n)
删除 O(n) O(1) O(1) O(log n) O(1) O(log n)
查找 O(1) O(n) O(1) O(log n) O(1) O(log n)
遍历

七、常见面试题

7.1 ArrayList和LinkedList的区别?

java 复制代码
// ArrayList:基于数组,查询快,增删慢
// LinkedList:基于链表,增删快,查询慢

// 场景1:频繁查询
List<String> list1 = new ArrayList<>();  // 推荐
for (int i = 0; i < 10000; i++) {
    list1.add("Item" + i);
}
String item = list1.get(5000);  // O(1)

// 场景2:频繁插入删除
List<String> list2 = new LinkedList<>();  // 推荐
for (int i = 0; i < 10000; i++) {
    list2.add(0, "Item" + i);  // 头部插入,O(1)
}

7.2 HashMap和Hashtable的区别?

java 复制代码
// HashMap:非线程安全,允许null键和null值,性能更好
Map<String, String> hashMap = new HashMap<>();
hashMap.put(null, "value");  // 允许
hashMap.put("key", null);    // 允许

// Hashtable:线程安全,不允许null键和null值,性能较差(已过时)
Map<String, String> hashtable = new Hashtable<>();
// hashtable.put(null, "value");  // 抛出NullPointerException

// 推荐使用ConcurrentHashMap替代Hashtable
Map<String, String> concurrentMap = new ConcurrentHashMap<>();

7.3 如何实现线程安全的集合?

java 复制代码
// 方法1:使用Collections工具类
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());

// 方法2:使用并发集合(推荐)
List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
Set<String> concurrentSet = ConcurrentHashMap.newKeySet();
Map<String, String> concurrentMap = new ConcurrentHashMap<>();

八、总结

Java集合框架的核心要点:

  1. List - 有序可重复,ArrayList查询快,LinkedList增删快
  2. Set - 无序不重复,HashSet性能好,TreeSet自动排序
  3. Map - 键值对存储,HashMap性能好,TreeMap自动排序
  4. 选择原则 - 根据业务场景选择合适的集合

最佳实践:

  • 优先使用接口类型声明变量
  • 合理设置初始容量,减少扩容
  • 自定义对象作为Key时重写hashCode和equals
  • 使用Stream API简化集合操作

相关资源


💡 小贴士: 集合框架是Java的基础,多练习才能熟练掌握!

关注我,获取更多Java干货!

相关推荐
VcB之殇2 小时前
git常用操作合集
前端·git
yinuo2 小时前
前端跨页面通讯终极指南⑧:Cookie 用法全解析
前端
小鑫同学2 小时前
vue-pdf-interactor 技术白皮书:为现代 Web 应用注入交互式 PDF 能力
前端·vue.js·github
GISer_Jing2 小时前
Nano Banana:AI图像生成与编辑新标杆
前端·javascript·人工智能
csdn_aspnet2 小时前
用100行實現HTML5可存檔塗鴉版
javascript
布茹 ei ai3 小时前
城市天气查询系统 (City Weather Dashboard)
javascript·vue.js·html·css3·开源软件·天气预报
gyx_这个杀手不太冷静3 小时前
上线前不做 Code Review?你可能正在给团队埋雷!
前端·代码规范·团队管理
全栈老石3 小时前
从硬编码到 Schema 推断:前端表单开发的工程化转型
前端·vue.js·架构
weixin_462446233 小时前
【原创实践】使用 shell 脚本批量创建 Linux 用户并生成随机密码
linux·服务器·前端