当前位置: 首页 > news >正文

Java集合框架、Collection体系的单列集合

Java集合框架、Collection

1. 认识Java集合框架及结构

Java集合框架是用于存储和操作对象的容器体系,主要分为CollectionMap两大根接口。其中Collection接口用于存储单个元素的集合,Map接口用于存储键值对映射关系。

1.1 集合框架整体结构

Collection接口是所有单列集合的根接口,它有两个主要子接口:List(有序、可重复)和Set(无序、不可重复)。每个子接口有多个实现类,具体结构如下:

Collection
├─ List(有序、可重复)
│  ├─ ArrayList(动态数组实现,查询快、增删慢)
│  ├─ LinkedList(双向链表实现,增删快、查询慢)
│  └─ Vector(线程安全的动态数组,已被ArrayList替代)
│
└─ Set(无序、不可重复)├─ HashSet(哈希表实现,无序、唯一,查询效率高)├─ LinkedHashSet(哈希表+双向链表实现,**有序(插入顺序)、唯一**,继承自HashSet)└─ TreeSet(红黑树实现,可排序、唯一,基于比较器或自然排序)

关键说明

  • 集合框架中的所有实现类均支持泛型,可指定存储元素的类型,避免类型转换异常。

1.2 集合框架的核心作用

  • 统一存储:提供标准化的容器接口,避免重复开发存储逻辑。
  • 简化操作:封装了增删改查等常用方法(如add()remove()size()),无需手动实现数据结构细节。
  • 灵活扩展:不同实现类适用于不同场景(如ArrayList适合查询,LinkedList适合增删,LinkedHashSet兼顾去重和顺序)。

2. Collection的两大常用集合体系及各个系列集合的特点

Collection接口下的两大核心体系为List系列Set系列,它们的特点及主要实现类对比如下:

2.1 List系列集合(有序、可重复)

  • 核心特点:元素有序(存储顺序=遍历顺序)、可重复(允许包含相同元素)、有索引(可通过下标操作元素)。
  • 主要实现类
    • ArrayList:基于动态数组实现,查询效率高(通过索引直接访问),增删效率低(需移动元素)。
    • LinkedList:基于双向链表实现,增删效率高(仅需修改指针),查询效率低(需从头/尾遍历)。

2.2 Set系列集合(无序、不可重复)

  • 核心特点:元素无序(默认存储顺序与遍历顺序无关)、不可重复(通过特定机制保证元素唯一性)、无索引(不能通过下标操作元素)。
  • 主要实现类
    • HashSet:基于哈希表实现,无序、唯一,查询/插入/删除效率高(时间复杂度O(1))。
    • LinkedHashSet:继承自HashSet,通过双向链表维护插入顺序,因此遍历顺序与插入顺序一致,同时保留HashSet的去重特性。
    • TreeSet:基于红黑树实现,可对元素进行排序(自然排序或定制排序),唯一,查询效率O(log n)。

3. Collection的常用方法

Collection接口定义了所有单列集合的通用方法,以下是最常用的10个方法及简单案例:

方法名作用代码示例(以ArrayList为例)
boolean add(E e)添加元素到集合末尾list.add("苹果"); // 添加"苹果"到集合
boolean remove(Object o)删除指定元素list.remove("苹果"); // 删除"苹果"
void clear()清空集合所有元素list.clear(); // 集合变为空
boolean contains(Object o)判断集合是否包含指定元素boolean hasApple = list.contains("苹果");
int size()返回集合元素个数int count = list.size(); // 获取元素数量
boolean isEmpty()判断集合是否为空boolean empty = list.isEmpty(); // 空返回true
Object[] toArray()将集合转换为数组Object[] arr = list.toArray(); // 集合转数组
boolean addAll(Collection<? extends E> c)添加另一个集合的所有元素到当前集合list.addAll(anotherList); // 合并两个集合
boolean removeAll(Collection<?> c)删除当前集合中与另一个集合共有的元素list.removeAll(anotherList); // 保留差集
boolean retainAll(Collection<?> c)保留当前集合中与另一个集合共有的元素list.retainAll(anotherList); // 保留交集

4. List系列集合的特点和特有方法

List系列集合因有索引,提供了更多基于索引的操作方法,核心特点及特有方法如下:

4.1 List系列核心特点

  • 有序:元素存储顺序与遍历顺序一致(如插入顺序为A→B→C,遍历结果也为A→B→C)。
  • 可重复:允许添加相同元素(如list.add("苹果"); list.add("苹果");会存储两个"苹果")。
  • 有索引:可通过get(int index)直接访问指定位置元素,类似数组。

4.2 List特有方法(以ArrayList为例)

方法名作用代码示例
void add(int index, E element)在指定索引位置插入元素list.add(1, "香蕉"); // 在索引1处插入"香蕉"
E remove(int index)删除指定索引位置的元素并返回该元素String removed = list.remove(1); // 删除索引1元素
E set(int index, E element)修改指定索引位置的元素并返回旧元素String old = list.set(1, "橙子"); // 替换索引1元素
E get(int index)返回指定索引位置的元素String fruit = list.get(0); // 获取索引0元素

5. ArrayList的四种遍历方式及删除元素时的并发修改异常

ArrayList是List接口的最常用实现类,支持多种遍历方式,但在遍历过程中删除元素可能导致并发修改异常(ConcurrentModificationException)。以下详细介绍四种遍历方式及删除元素的注意事项。

5.1 四种遍历方式

ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"))为例

5.1.1 普通for循环(基于索引)
// 正序遍历
for (int i = 0; i < list.size(); i++) {String element = list.get(i); // 通过索引获取元素System.out.println(element); // 输出:A B C D
}
5.1.2 迭代器(Iterator)
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) { // 判断是否有下一个元素String element = iterator.next(); // 获取下一个元素System.out.println(element); // 输出:A B C D
}
5.1.3 增强for循环(for-each)
for (String element : list) { // 简化遍历语法,无需索引System.out.println(element); // 输出:A B C D
}
5.1.4 Lambda表达式(Java 8+)
list.forEach(element -> System.out.println(element)); // 输出:A B C D
// 或简化为方法引用
list.forEach(System.out::println);

5.2 删除元素时的并发修改异常及解决方案

并发修改异常原因:集合在遍历过程中,若通过集合自身的remove()方法修改元素数量(如删除),会导致迭代器的修改次数标记与集合实际修改次数不一致,触发异常。

5.2.1 有索引集合(如ArrayList)的删除解决方案

场景:删除集合中所有" B "元素(原集合:[“A”, “B”, “B”, “C”])。

  • 错误方式:正序遍历+集合remove()
  for (int i = 0; i < list.size(); i++) {if ("B".equals(list.get(i))) {list.remove(i); // 删除后集合长度变化,导致后续元素漏判}}// 结果:删除第一个"B"后,索引i=1的元素变为原索引2的"B",但i自增为2,漏删第二个"B"
  • 正确方式1:正序遍历+i--(删除后回退索引)
  for (int i = 0; i < list.size(); i++) {if ("B".equals(list.get(i))) {list.remove(i); i--; // 删除后索引回退,避免漏判}}// 结果:成功删除所有"B",集合变为["A", "C"]
  • 正确方式2:倒序遍历(从后往前删,不影响前面元素索引)
  for (int i = list.size() - 1; i >= 0; i--) {if ("B".equals(list.get(i))) {list.remove(i); // 后面元素删除不影响前面元素索引}}// 结果:成功删除所有"B",集合变为["A", "C"]
5.2.2 无索引集合(如Set)的删除解决方案

场景:删除HashSet中所有" B "元素(原集合:[“A”, “B”, “C”])。

  • 错误方式:增强for循环+集合remove()
  for (String element : set) {if ("B".equals(element)) {set.remove(element); // 触发ConcurrentModificationException}}
  • 正确方式:迭代器的remove()方法(唯一解决方案)
  Iterator<String> iterator = set.iterator();while (iterator.hasNext()) {String element = iterator.next();if ("B".equals(element)) {iterator.remove(); // 使用迭代器自带的删除方法,不会触发异常}}// 结果:成功删除"B",集合变为["A", "C"]
5.2.3 增强for和Lambda遍历的局限性

增强for循环(for-each)和Lambda表达式遍历仅适合读取元素,不适合同时增删元素。因为它们底层依赖迭代器,但不允许通过集合自身方法修改元素数量,否则必然触发并发修改异常。

6. ArrayList和LinkedList的区别

ArrayList和LinkedList是List接口的两个核心实现类,它们的底层结构、特点和应用场景有显著差异。

6.1 核心区别对比

对比维度ArrayListLinkedList
底层结构动态数组(可扩容的数组)双向链表(每个节点包含前后指针)
查询效率高(通过索引直接访问,O(1))低(需从头/尾遍历,O(n))
增删效率中间增删低(需移动元素,O(n))中间增删高(仅需修改指针,O(1))
内存占用低(仅存储元素值)高(每个元素需额外存储前后指针)
特有方法无(依赖List接口方法)addFirst()/addLast()/removeFirst()/removeLast()等首尾操作方法

6.2 LinkedList的首尾操作特有方法

LinkedList因链表结构,新增了直接操作首尾元素的方法,效率极高(O(1)):

LinkedList<String> linkedList = new LinkedList<>();
linkedList.addFirst("头部元素"); // 添加到链表头部
linkedList.addLast("尾部元素"); // 添加到链表尾部
String first = linkedList.getFirst(); // 获取头部元素
String last = linkedList.getLast(); // 获取尾部元素
String removedFirst = linkedList.removeFirst(); // 删除并返回头部元素
String removedLast = linkedList.removeLast(); // 删除并返回尾部元素

6.3 LinkedList的应用场景

6.3.1 队列(FIFO:先进先出)

队列是一种特殊的线性表,仅允许在队尾添加元素、在队头删除元素。LinkedList的addLast()(入队)和removeFirst()(出队)方法完美适配队列操作:

// 模拟队列:添加元素到尾部,删除元素从头部
LinkedList<String> queue = new LinkedList<>();
queue.addLast("任务1"); // 入队
queue.addLast("任务2");
String task = queue.removeFirst(); // 出队,返回"任务1"
6.3.2 栈(LIFO:后进先出)

栈是一种特殊的线性表,仅允许在栈顶添加和删除元素。LinkedList的addLast()(入栈)和removeLast()(出栈)方法可模拟栈操作:

// 模拟栈:添加和删除元素都在尾部(栈顶)
LinkedList<String> stack = new LinkedList<>();
stack.addLast("元素1"); // 入栈
stack.addLast("元素2");
String top = stack.removeLast(); // 出栈,返回"元素2"(最后入栈的元素先出)

7. Set系列集合的特点(含LinkedHashSet)

Set系列集合的核心特点是无序、不可重复,但不同实现类的"无序"定义不同(HashSet完全无序,LinkedHashSet保留插入顺序,TreeSet按排序顺序)。以下详细介绍HashSet、LinkedHashSet、TreeSet的特点及实现原理。

7.1 Set系列集合的整体特点

实现类底层结构有序性去重机制线程安全
HashSet哈希表(数组+链表/红黑树)完全无序(存储顺序≠遍历顺序)基于hashCode()和equals()
LinkedHashSet哈希表+双向链表有序(插入顺序=遍历顺序同HashSet(继承自HashSet)
TreeSet红黑树(平衡二叉树)有序(自然排序/定制排序)基于比较器(Comparable/Comparator)

7.2 LinkedHashSet的特点与实现原理

7.2.1 核心特点
  • 有序性:通过内部维护的双向链表记录元素的插入顺序,遍历元素时严格按照插入顺序输出。
  • 唯一性:继承自HashSet,因此保留HashSet的去重机制(基于hashCode()和equals())。
  • 性能:插入/删除效率略低于HashSet(因需额外维护链表),但遍历效率高于HashSet(无需随机访问哈希表)。
7.2.2 实现原理

LinkedHashSet继承自HashSet,其构造方法会调用HashSet的一个特殊构造方法,创建一个LinkedHashMap实例(HashSet底层实际是HashMap,LinkedHashSet底层实际是LinkedHashMap):

// LinkedHashSet的构造方法
public LinkedHashSet() {super(16, 0.75f, true); // 调用HashSet的构造方法,第三个参数为true表示使用LinkedHashMap
}// HashSet中对应的构造方法
HashSet(int initialCapacity, float loadFactor, boolean dummy) {map = new LinkedHashMap<>(initialCapacity, loadFactor); // 底层创建LinkedHashMap
}

LinkedHashMap在HashMap的基础上,为每个元素增加了beforeafter指针,形成双向链表,从而记录插入顺序。

7.2.3 代码案例:LinkedHashSet的有序性和去重性
import java.util.LinkedHashSet;public class LinkedHashSetDemo {public static void main(String[] args) {LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();linkedHashSet.add("C");linkedHashSet.add("A");linkedHashSet.add("B");linkedHashSet.add("A"); // 重复元素,会被去重// 遍历集合,观察顺序for (String element : linkedHashSet) {System.out.print(element + " "); // 输出:C A B(与插入顺序一致,且"A"只出现一次)}}
}

7.3 HashSet的实现原理和去重机制

7.3.1 JDK8前后的实现原理对比
版本底层结构(数组+链表/红黑树)链表转红黑树阈值
JDK8之前数组+单向链表(元素哈希冲突时,在数组位置后追加链表)无(始终用链表)
JDK8及之后数组+链表+红黑树(当链表长度>8且数组容量≥64时,链表转为红黑树)链表长度>8
7.3.2 去重机制

HashSet通过以下步骤保证元素唯一性:

  1. 调用元素的hashCode()方法计算哈希值,确定在哈希表中的存储位置。
  2. 若该位置为空,直接存入元素。
  3. 若该位置不为空,调用equals()方法比较元素内容:
    • equals()返回true(内容相同),视为重复元素,不存入。
    • equals()返回false(内容不同但哈希冲突),存入链表/红黑树。

注意:若两个对象内容相同但hashCode()返回不同值,HashSet会视为不同元素(未去重)。
解决方案:重写hashCode()equals()方法,使内容相同的对象返回相同哈希值且equals()返回true。

代码案例:重写hashCode()equals()实现对象去重

import java.util.HashSet;class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}// 重写equals():比较对象内容(name和age)@Overridepublic 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 && name.equals(student.name);}// 重写hashCode():基于name和age计算哈希值@Overridepublic int hashCode() {return 31 * name.hashCode() + age; // 31是质数,减少哈希冲突}
}public class HashSetDemo {public static void main(String[] args) {HashSet<Student> students = new HashSet<>();students.add(new Student("张三", 18));students.add(new Student("张三", 18)); // 内容相同,会被去重System.out.println(students.size()); // 输出:1(去重成功)}
}

7.4 TreeSet的排序机制

TreeSet是唯一可排序的Set实现类,排序方式分为自然排序定制排序,核心依赖比较器接口。

7.4.1 自然排序(实现Comparable接口)

让元素类实现Comparable接口,重写compareTo()方法定义排序规则:

import java.util.TreeSet;class Person implements Comparable<Person> {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}// 重写compareTo():按年龄升序排序@Overridepublic int compareTo(Person o) {return this.age - o.age; // 返回负数:当前对象在前;正数:当前对象在后;0:视为重复元素}
}public class TreeSetNaturalSort {public static void main(String[] args) {TreeSet<Person> treeSet = new TreeSet<>();treeSet.add(new Person("张三", 20));treeSet.add(new Person("李四", 18));treeSet.add(new Person("王五", 22));// 遍历集合,按年龄升序输出for (Person p : treeSet) {System.out.println(p.age); // 输出:18 20 22(自然排序结果)}}
}
7.4.2 定制排序(使用Comparator比较器)

创建TreeSet时传入Comparator接口实现类,自定义排序规则(无需修改元素类):

import java.util.Comparator;
import java.util.TreeSet;public class TreeSetCustomSort {public static void main(String[] args) {// 创建TreeSet时传入Comparator比较器,按年龄降序排序TreeSet<Person> treeSet = new TreeSet<>(new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return o2.age - o1.age; // 降序:后一个对象年龄 - 前一个对象年龄}});treeSet.add(new Person("张三", 20));treeSet.add(new Person("李四", 18));treeSet.add(new Person("王五", 22));for (Person p : treeSet) {System.out.println(p.age); // 输出:22 20 18(定制排序结果)}}
}
http://www.dtcms.com/a/324555.html

相关文章:

  • 有限元方法中的数值技术:追赶法求解三对角方程
  • 【鸿蒙/OpenHarmony/NDK】什么是NDK? 为啥要用NDK?
  • PCB知识07 地层与电源层
  • LLIC:基于自适应权重大感受野图像变换编码的学习图像压缩
  • 每日一题:使用栈实现逆波兰表达式求值
  • Redis高级
  • AAAI 2025丨具身智能+多模态感知如何精准锁定目标
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘ray’问题
  • Python数据分析常规步骤整理
  • Mysql系列--5、表的基本查询(下)
  • Speaking T2 - Dining Hall to CloseDuring Spring Break
  • 机器学习 DBScan
  • 一键复制产品信息到剪贴板
  • 【接口自动化】初识pytest,一文讲解pytest的安装,识别规则以及配置文件的使用
  • 网闸技术解析:如何实现对国产数据库(达梦/金仓)的深度支持
  • AI 代理框架:使用正确的工具构建更智能的系统
  • 网络小工具发布 IPPw
  • 机器学习之K-means(K-均值)算法
  • 七、CV_模型微调
  • SpringBoot学习日记(三)
  • P1152 欢乐的跳
  • 从零开始实现Qwen3(MOE架构)
  • C语言基础05——指针
  • Pinia 状态管理库
  • Redis - 使用 Redis HyperLogLog 进行高效基数统计
  • 无人机集群协同三维路径规划,采用梦境优化算法(DOA)实现,Matlab代码
  • strace的常用案例
  • 基于Qt/QML 5.14和YOLOv8的工业异常检测Demo:冲压点智能识别
  • VSCODE+GDB+QEMU调试内核
  • 为 Prometheus 告警规则增加 UI 管理能力