玩转 Java 集合:底层原理与业务实战全解析

11 阅读7分钟

玩转 Java 集合:底层原理与业务实战全解析

在Java开发中,数组作为基础的数据存储容器,存在长度固定只能存储同类型元素等局限性。而集合框架则提供了更灵活、高效的数据存储方案,能适配不同业务场景的存储需求。本文将结合实战代码,从体系结构、核心API、底层原理到业务实战,全面拆解Java集合框架。

一、集合框架整体体系

Java集合框架主要分为**单列集合(Collection)双列集合(Map)**两大体系,其核心结构如下:

  • 单列集合:每个元素仅存储一个数据,顶层接口为Collection,分为ListSet两大分支
  • 双列集合:每个元素以键值对(Key-Value)形式存储,顶层接口为Map

1.1 单列集合:Collection家族

  • List:有序、可重复、有索引

    • ArrayList:基于数组实现,查询快、增删慢
    • LinkedList:基于双向链表实现,增删快、查询慢
  • Set:无序、不重复、无索引

    • HashSet:基于哈希表,查询快
    • LinkedHashSet:基于哈希表+链表,有序
    • TreeSet:基于红黑树,自动排序

1.2 双列集合:Map家族

  • Map:键值对存储,键唯一、值可重复

    • HashMap:基于哈希表,查询快
    • LinkedHashMap:基于哈希表+链表,有序
    • TreeMap:基于红黑树,自动排序

1.3单列集合:List与Set的核心差异

单列集合的特性由ListSet的子实现类决定,我们可以通过一段代码直观感受两者区别:

// List集合:有序、可重复、有索引
List<String> list = new ArrayList<>();
list.add("java");
list.add("java");
list.add("C");
System.out.println(list); // 输出:[java, java, C]
System.out.println(list.get(0)); // 输出:java(支持索引访问)

// Set集合:无序、不重复、无索引
Set<String> set = new HashSet<>();
set.add("鸿蒙");
set.add("java");
set.add("java");
System.out.println(set); // 输出:[鸿蒙, java](自动去重,顺序与添加无关)
  • List系列(ArrayList/LinkedList):侧重有序存储,支持索引操作,允许元素重复
  • Set系列(HashSet/LinkedHashSet/TreeSet):侧重去重存储,无索引,其中TreeSet支持排序、LinkedHashSet可保证插入顺序

1.4双列集合:Map的键值对存储

Map集合以“键唯一、值可重复”为核心特性,常用实现类包括HashMap(无序)、LinkedHashMap(有序)、TreeMap(排序)。示例代码如下:

Map<String, Integer> map = new HashMap<>();
map.put("张三", 18);
map.put("张三", 19); // 键重复会覆盖旧值
map.put("李四", 20);
System.out.println(map); // 输出:{李四=20, 张三=19}

二、Collection单列集合核心操作

Collection作为单列集合的顶层接口,定义了通用API,所有子实现类均可继承使用。

2.1 Collection通用API实战

我们可以通过ArrayList实现类,验证addremovecontains等核心方法:

Collection<String> coll = new ArrayList<>();
// 1. 添加元素
coll.add("张三");
coll.add("李四");
// 2. 获取元素个数
System.out.println(coll.size()); // 输出:2
// 3. 删除元素
coll.remove("李四");
// 4. 判断元素是否存在
System.out.println(coll.contains("张三")); // 输出:true
// 5. 转数组
Object[] arr = coll.toArray();
System.out.println(Arrays.toString(arr)); // 输出:[张三]

2.2 集合的三种遍历方式

Collection支持迭代器增强forLambda表达式三种遍历方式,不同方式适配不同场景:

  1. 迭代器遍历:集合专属遍历工具,支持遍历中删除元素
Collection<String> names = new ArrayList<>();
names.add("张三");
names.add("李四");
Iterator<String> it = names.iterator();
while (it.hasNext()) {
    String name = it.next();
    System.out.println(name);
    // 迭代器自身remove方法可避免并发修改异常
    if (name.equals("李四")) it.remove();
}
  1. 增强for遍历:简化版迭代器,仅适用于读取数据
for (String name : names) {
    System.out.println(name);
}
  1. Lambda遍历:JDK8+特性,代码更简洁
names.forEach(System.out::println);

2.3 避坑指南:并发修改异常

当遍历集合时直接调用集合的remove方法,会触发并发修改异常。以下是两种解决方案:

ArrayList<String> list = new ArrayList<>();
list.add("宁夏枸杞");
list.add("黑枸杞");
list.add("人字拖");

// 方案1:索引遍历+删除后i--
for (int i = 0; i < list.size(); i++) {
    if (list.get(i).contains("枸杞")) {
        list.remove(i);
        i--; // 修正索引偏移
    }
}

// 方案2:迭代器remove方法
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().contains("枸杞")) it.remove();
}

三、List集合:有序存储的专属实现

List集合因支持索引,拥有add(int index, E e)remove(int index)等特有方法,同时其两个核心实现类(ArrayList/LinkedList)底层原理差异显著。

3.1 List特有API实战

List<String> names = new ArrayList<>();
names.add("张三");
names.add("李四");
// 指定索引插入元素
names.add(1, "王二"); 
// 修改指定索引元素
names.set(2, "孙七"); 
// 删除指定索引元素
names.remove(0); 
System.out.println(names); // 输出:[王二, 孙七]

3.2 ArrayList与LinkedList底层原理

  • ArrayList:基于数组实现,查询快(通过索引直接定位)、增删慢(需移动后续元素)
  • LinkedList:基于双向链表实现,查询慢(需遍历链表)、增删快(仅需修改节点指针),且可直接实现队列/栈结构:
// LinkedList实现队列(先进先出)
LinkedList<String> queue = new LinkedList<>();
queue.addLast("张三");
queue.addLast("李四");
System.out.println(queue.removeFirst()); // 输出:张三

// LinkedList实现栈(后进先出)
LinkedList<String> stack = new LinkedList<>();
stack.push("张三");
stack.push("李四");
System.out.println(stack.pop()); // 输出:李四

四、Set集合:去重存储的核心方案

Set集合的核心价值是元素去重,不同实现类的去重与排序逻辑依赖不同底层机制。

4.1 HashSet:基于哈希表的高效去重

HashSet底层基于**哈希表(数组+链表+红黑树,JDK8+)**实现,其去重逻辑依赖两个方法:

  1. hashCode():计算元素的哈希值,确定存储位置
  2. equals():判断同一哈希位置的元素是否重复

若要实现自定义对象的去重,需重写这两个方法。以Student类为例:

public class Student {
    private String name;
    private int age;

    // 重写equals:判断属性是否一致
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    // 重写hashCode:保证属性一致的对象哈希值相同
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

// 测试去重效果
Set<Student> studentSet = new HashSet<>();
studentSet.add(new Student("张三", 18));
studentSet.add(new Student("张三", 18));
System.out.println(studentSet.size()); // 输出:1(自动去重)

4.2 TreeSet:支持排序的去重集合

TreeSet底层基于红黑树实现,默认支持数值/字符串的升序排序。若要存储自定义对象,需指定排序规则,有两种实现方式:

  1. 自定义类实现Comparable接口
public class Teacher implements Comparable<Teacher> {
    private double salary;

    @Override
    public int compareTo(Teacher o) {
        return Double.compare(this.salary, o.salary); // 按薪资升序
    }
}
  1. TreeSet构造时传入Comparator比较器
Set<Teacher> teachers = new TreeSet<>((o1, o2) -> 
    Double.compare(o2.getSalary(), o1.getSalary())); // 按薪资降序
teachers.add(new Teacher(10000.7));
teachers.add(new Teacher(13000.1));
System.out.println(teachers); // 输出:[13000.1, 10000.7]

五、Map双列集合:键值对的高效存储

Map集合以“键值对”形式存储数据,其核心操作围绕“键”展开,同时支持三种遍历方式。

5.1 Map核心API实战

Map<String, Integer> map = new HashMap<>();
// 添加键值对
map.put("张三", 18);
// 根据键取值
System.out.println(map.get("张三")); // 输出:18
// 判断键是否存在
System.out.println(map.containsKey("李四")); // 输出:false
// 获取所有键
Set<String> keys = map.keySet();
// 获取所有值
Collection<Integer> values = map.values();

5.2 Map的三种遍历方式

  1. 键找值:先获取所有键,再遍历取值
for (String key : map.keySet()) {
    System.out.println(key + "=" + map.get(key));
}
  1. 键值对遍历:将键值对作为整体遍历,效率更高
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}
  1. Lambda遍历:JDK8+简化写法
map.forEach((k, v) -> System.out.println(k + "=" + v));

5.3 实战案例:景点投票统计

利用Map的键值对特性,可快速实现“80人秋游景点投票统计”功能:

// 模拟80人投票数据
List<String> locations = new ArrayList<>();
String[] spots = {"玉龙雪山","故宫","黄鹤楼","外滩"};
Random r = new Random();
for (int i = 0; i < 80; i++) {
    locations.add(spots[r.nextInt(spots.length)]);
}

// 统计投票结果
Map<String, Integer> voteMap = new HashMap<>();
for (String spot : locations) {
    voteMap.put(spot, voteMap.containsKey(spot) ? voteMap.get(spot)+1 : 1);
}

// 输出结果
voteMap.forEach((k, v) -> System.out.println(k + "获票" + v + "张"));

六、业务实战:基于集合的电影管理系统

结合List集合的有序存储特性,我们可以实现一个简易的电影管理系统,支持电影上架查询封杀明星关联电影等功能。

6.1 核心实体类:Movie

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Movie {
    private String name;
    private double score;
    private String actor;
    private double price;
}

6.2 核心业务逻辑:MovieService

public class MovieService {
    private static List<Movie> movies = new ArrayList<>();
    private static Scanner sc = new Scanner(System.in);

    // 上架电影
    public void addMovie() {
        Movie movie = new Movie();
        System.out.println("输入电影名称:");
        movie.setName(sc.next());
        System.out.println("输入电影评分:");
        movie.setScore(sc.nextDouble());
        movies.add(movie);
        System.out.println("上架成功!");
    }

    // 封杀明星关联电影
    public void deleteStar() {
        System.out.println("输入封杀明星姓名:");
        String star = sc.next();
        for (int i = 0; i < movies.size(); i++) {
            if (movies.get(i).getActor().contains(star)) {
                movies.remove(i);
                i--;
            }
        }
        System.out.println("封杀完成!");
    }
}

七、集合框架选型建议

不同集合的特性决定了其适用场景,选型时可遵循以下原则:

  1. 有序可重复+频繁查询:选ArrayList
  2. 有序可重复+频繁增删:选LinkedList(尤其首尾操作)
  3. 去重+无序存储:选HashSet
  4. 去重+有序存储:选LinkedHashSet
  5. 去重+排序存储:选TreeSet
  6. 键值对存储+高效查询:选HashMap
  7. 键值对存储+排序需求:选TreeMap

总结

Java集合框架是开发中的必备工具,其核心价值在于灵活适配不同存储场景。从底层原理来看,ArrayList的数组、LinkedList的链表、HashSet的哈希表、TreeSet的红黑树,分别对应了不同的数据结构思想;从实战角度,掌握API操作、避坑并发异常、结合业务选型,才能真正发挥集合的高效性。