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

Java 进阶--集合:告别数组的“僵硬”,拥抱灵活的数据容器

作者:IvanCodes

发布时间:2025年5月1日🫡

专栏:Java教程


大家好!👋 还记得我们上次聊的数组 (Array) 吗?它很基础,性能也不错,但有个致命的缺点长度一旦定了就不能改 <🔒>!想象一下,你用数组存购物车里的商品 🛒,刚开始定了装 10 件,结果顾客想买第 11 件… 咋办?重新创建一个更大的数组,再把旧的东西搬过去?太麻烦了!😫

为了解决数组这种“僵硬”的问题,以及提供更丰富的数据组织方式,Java 提供了一套强大的 集合框架 (Java Collections Framework, JCF)。你可以把它想象成一个超级工具箱 🧰,里面有各种大小可变功能各异容器 🧺,专门用来高效地存储和管理对象。今天,我们就来打开这个工具箱,看看里面有哪些宝贝!✨

一、为什么需要集合?它比数组强在哪? 🤔🚀

相比数组集合主要有这些优势

  1. 动态大小 <📏➡️<♾️>>:大部分集合的大小是可变的,你可以随时添加或删除元素,不用担心容量问题。
  2. 丰富的功能 <⚙️>: 集合框架提供了大量现成的方法来操作数据,比如添加、删除、查找、排序、迭代等,比数组方便得多。
  3. 不同的数据结构 <🗺️>: 集合框架提供了多种数据结构(如列表队列映射),每种都有不同的特点(如是否有序、是否允许重复),可以根据需求选择最合适的。
  4. 泛型支持 : 集合天生与泛型结合,保证了类型安全,避免了从集合中取出元素时的强制类型转换和潜在错误。

二、集合框架的核心:接口实现 <🧱>

Java 集合框架的设计非常优雅,它主要由一系列接口(定义规范)和实现类(提供具体的数据结构和算法)组成。我们写代码时,应该尽量面向接口编程,这样更灵活

核心接口主要有这几位大佬:

  1. Collection<E> <🧺>: 单列集合根接口,定义了所有单列集合(一次存一个元素)的基本操作,如 add(), remove(), contains(), size(), isEmpty(), iterator()。它派生出两个主要的子接口:ListSet
  2. List<E> <📝>: 有序集合(元素按插入顺序排列),允许存储重复的元素。可以把它想象成一个带编号的清单,可以根据索引(编号)精确访问。
  3. Set<E> <🚫>: 无序(通常不保证顺序)的集合不允许存储重复的元素。就像一个独特的邮票收藏册 ,每张邮票都是独一无二的。
  4. Queue<E> <🚶‍♀️➡️🚶‍♂️>: 队列接口,通常遵循先进先出(FIFO)的原则(也有例外,如优先队列)。就像排队买票 <🎫>,先来的先买。
  5. Map<K, V> <🔑➡️<🎁>>: 映射接口,存储的是键值对 (Key-Value Pair)。注意⚠️:Map 不直接继承 Collection 接口,它自成一派,但通常被认为是集合框架的一部分。Key 必须唯一,Value 可以重复。就像一本字典 📖,通过唯一的“单词”(Key)可以查到对应的“解释”(Value)。

接下来,我们详细看看最常用的 List, Set, Map 及其主要实现。

三、List 接口:有序可重复 <📝>

特点:存入和取出的顺序一致,可以包含相同的元素。

常用实现类

  • ArrayList<E> <🧱>:
    • 底层:基于动态数组实现。
    • 优点👍:查询(根据索引 get()速度快⚡️(随机访问快)。
    • 缺点👎:增删add() / remove(),尤其是在中间位置)速度相对较慢🐢,因为可能需要移动大量元素。
    • 适用场景🎯:查找多增删少的情况。最常用的 List 实现。
  • LinkedList<E> <🔗>:
    • 底层:基于双向链表实现。
    • 优点👍:增删(尤其是在首尾速度快🚀,不需要移动元素,只需修改指针。
    • 缺点👎:查询(根据索引 get()速度相对较慢🐌,需要从头或尾开始遍历链表。
    • 适用场景🎯:增删多查找少的情况。它也实现了 Queue 接口,常被用作队列或栈。

代码示例 (ArrayList):

import java.util.ArrayList;
import java.util.List;public class ListDemo {public static void main(String[] args) {// 创建一个存储 String 的 ArrayList (面向接口编程)List<String> names = new ArrayList<>(); // new// 添加元素names.add("Alice"); // 索引 0names.add("Bob");   // 索引 1names.add("Charlie"); // 索引 2names.add("Bob"); // 允许重复System.out.println("Names list: " + names); // 输出保持添加顺序// 获取元素 (按索引)String secondName = names.get(1); // 获取索引为 1 的元素System.out.println("Second name: " + secondName); // Bob// 修改元素names.set(3, "David"); // 修改索引为 3 的元素System.out.println("After modification: " + names);// 删除元素names.remove(0); // 删除索引为 0 的元素System.out.println("After removing first element: " + names);// 获取大小System.out.println("Current size: " + names.size());// 遍历 (增强 for)System.out.print("Iterating through names: ");for(String name : names) {System.out.print(name + " ");}System.out.println();}
}

四、Set 接口:无序 (通常),不重复 <🚫>

特点:不能包含重复的元素。大部分实现不保证元素的存取顺序。

常用实现类

HashSet<E> <🧺><#️⃣>:

  • 底层:基于哈希表 (HashMap 实现)。
  • 优点👍:添加删除查找contains()速度非常快⚡️(平均 O(1))。
  • 缺点👎:不保证元素的顺序
  • 要求:存入 HashSet 的元素必须正确重写 hashCode()equals() 方法。
  • 适用场景🎯:需要快速去重快速判断元素是否存在,且不关心顺序最常用的 Set 实现。

代码示例 (HashSet):

import java.util.HashSet;
import java.util.Set;public class HashSetDemo {public static void main(String[] args) {// 创建一个存储 Integer 的 HashSet (面向接口编程)Set<Integer> uniqueNumbers = new HashSet<>(); // new// 添加元素uniqueNumbers.add(5);uniqueNumbers.add(10);uniqueNumbers.add(5); // 尝试添加重复元素 5uniqueNumbers.add(15);uniqueNumbers.add(10); // 尝试添加重复元素 10// 输出 Set,重复元素自动被忽略,顺序不一定是添加顺序System.out.println("Unique numbers set: " + uniqueNumbers); // 可能输出 [5, 10, 15] 或其他顺序// 检查是否包含元素boolean hasTen = uniqueNumbers.contains(10);System.out.println("Set contains 10? " + hasTen); // true// 删除元素uniqueNumbers.remove(5);System.out.println("After removing 5: " + uniqueNumbers);System.out.println("Current size: " + uniqueNumbers.size()); // 2// 遍历 (顺序不保证)System.out.print("Iterating through the set: ");for(Integer num : uniqueNumbers) {System.out.print(num + " ");}System.out.println();}
}

LinkedHashSet<E> <🔗><#️⃣>:

  • 底层:基于哈希表双向链表
  • 优点👍:既有 HashSet 的快速查找 (O(1)),又能保持元素的插入顺序 <➡️>!遍历时会按照添加的顺序输出。
  • 缺点👎:性能略低于 HashSet(因为维护链表的额外开销),内存占用也稍大。
  • 要求:同样需要元素正确重写 hashCode()equals()
  • 适用场景🎯:需要去重,同时希望保持元素添加时的顺序

代码示例 (LinkedHashSet):

import java.util.LinkedHashSet;
import java.util.Set;public class LinkedHashSetDemo {public static void main(String[] args) {// 创建 LinkedHashSet,保持插入顺序Set<String> orderedUniqueNames = new LinkedHashSet<>(); // neworderedUniqueNames.add("Charlie");orderedUniqueNames.add("Alice");orderedUniqueNames.add("Bob");orderedUniqueNames.add("Alice"); // 重复的 Alice 被忽略// 输出时会保持添加顺序!System.out.println("Ordered unique names: " + orderedUniqueNames);// Output: Ordered unique names: [Charlie, Alice, Bob]}
}

TreeSet<E> <🌳>:

  • 底层:基于红黑树 (TreeMap 实现)。
  • 优点👍:元素会自动排序!可以按照元素的自然顺序(元素需实现 Comparable 接口)或者指定的比较器(创建 TreeSet 时传入 Comparator)进行排序。
  • 缺点👎:增删查的速度略慢HashSet(O(log n))。
  • 要求:存入 TreeSet 的元素必须可比较的
  • 适用场景🎯:需要去重的同时保持元素有序

代码示例 (TreeSet):

import java.util.Set;
import java.util.TreeSet;public class TreeSetDemo {public static void main(String[] args) {// 创建 TreeSet,元素会自动排序Set<Integer> sortedNumbers = new TreeSet<>(); // newsortedNumbers.add(50);sortedNumbers.add(20);sortedNumbers.add(80);sortedNumbers.add(20); // 重复的 20 被忽略// 输出时元素是排序好的!System.out.println("Sorted unique numbers: " + sortedNumbers);// Output: Sorted unique numbers: [20, 50, 80]}
}

五、Map 接口:键值对存储 <🔑➡️<🎁>>

特点:存储的是 Key-Value 对,Key 必须唯一Value 可以重复。通过 Key 可以快速查找到对应的 Value。

常用实现类

HashMap<K, V> <🧺><#️⃣>:

  • 底层:基于哈希表

  • 优点👍:增删查(根据 Key)速度极快⚡️(平均 O(1))。

  • 缺点👎:不保证键值对的存储顺序

  • 要求:Key 必须正确重写 hashCode()equals()

  • 允许 Key 和 Value 为 null (Key 只能有一个 null )。

  • 适用场景🎯:需要快速 Key-Value 查找不关心顺序最常用

  • LinkedHashMap<K, V> <🔗><#️⃣>:

    • 底层:基于哈希表双向链表
    • 优点👍:既有 HashMap 的快速查找,又能保持键值对的插入顺序 <➡️>(或访问顺序,构造时可指定)。
    • 缺点👎:性能略低于 HashMap,内存占用稍大。
    • 要求:Key 同样需要 hashCode()equals()
    • 适用场景🎯:需要保持插入顺序映射关系,如实现LRU缓存(使用访问顺序)。
  • TreeMap<K, V> <🌳>:

    • 底层:基于红黑树
    • 优点👍:键值对会根据 Key 自动排序
    • 缺点👎:增删查速度略慢(O(log n))。
    • 要求:Key 必须可比较的
    • 不允许 Key 为 null
    • 适用场景🎯:需要按 Key 排序映射关系。
  • Hashtable<K, V> <🔒><#️⃣>:

    • 底层:也是基于哈希表
    • 特点线程安全!所有方法都是 synchronized 的。
    • 缺点👎:因为所有方法都加锁,并发性能很差 <🐢>。现在基本不推荐使用
    • 不允许 Key 或 Value 为 null <🚫>。
    • 替代方案💡:需要线程安全的 Map,优先考虑 <font color="purple">ConcurrentHashMap</font> (来自 java.util.concurrent 包),它提供了更好的并发性能

(HashMap 和 TreeMap 示例省略,见上一版本)

六、 集合工具类:Collections <🛠️>

别和 Collection 接口搞混了!Collections (带 s) 是一个工具类,里面全是 static 方法,用来方便地操作集合

一些常用方法

  • Collections.sort(List<T> list): 对 List 进行排序 <📊>。
  • Collections.reverse(List<?> list): 反转 List 中元素的顺序 <🔄>。
  • Collections.shuffle(List<?> list): 随机打乱 List 中元素的顺序 <🔀>。
  • Collections.max(Collection<? extends T> coll) / min(...): 找到集合中的最大/最小值 <🥇><🥉>。
  • Collections.frequency(Collection<?> c, Object o): 计算指定元素在集合中出现的次数 <🔢>。
  • Collections.synchronizedList/Set/Map(...): 返回指定集合线程安全版本 <🔒> (性能不如 java.util.concurrent 包下的类)。
  • Collections.unmodifiableList/Set/Map(...): 返回指定集合只读视图 <🚫>✍️。

代码示例:

import java.util.ArrayList;
import java.util.Collections; // 导入工具类
import java.util.List;public class CollectionsUtilDemo {public static void main(String[] args) {List<Integer> numbers = new ArrayList<>();numbers.add(5);numbers.add(1);numbers.add(8);numbers.add(3);System.out.println("Original list: " + numbers);// 排序Collections.sort(numbers);System.out.println("Sorted list: " + numbers);// 反转Collections.reverse(numbers);System.out.println("Reversed list: " + numbers);// 查找最大值Integer max = Collections.max(numbers);System.out.println("Maximum value: " + max);}
}

七、 别忘了迭代器 (Iterator) <🔍>

迭代器遍历所有 Collection (List, Set, Queue) 的标准、通用方式。虽然增强型 for 循环更简洁,但迭代器允许你在遍历过程中安全地删除元素(使用 iterator.remove())。

(迭代器代码示例省略,见上一版本)

八、总结:选择合适的容器 🏁

Java 集合框架提供了一套丰富、灵活、高效的数据容器。选择时主要考虑:

  • 是否有序? (List 有序, 大部分 Set/Map 无序, LinkedHashSet/LinkedHashMap 插入有序, TreeSet/TreeMap 排序)
  • 是否允许重复? (List 允许, Set 不允许)
  • 存储结构? (单个元素用 List/Set, 键值对用 Map)
  • 性能需求? (查找为主选 ArrayList/HashSet/HashMap, 增删为主选 LinkedList, 需要排序选 TreeSet/TreeMap)
  • 是否需要线程安全? (优先考虑 ConcurrentHashMapjava.util.concurrent 包下的类)

核心原则面向接口编程始终使用泛型!根据需求选择最合适实现类


九、练练手,检验成果!✏️🧠

检验一下学习成果吧!

⭐ 选择与应用 ⭐

  1. 如果你需要存储用户访问网站的页面顺序(URL 字符串),并且可能会频繁在列表开头添加新的访问记录(最新的访问总在最前面),你会选择 ArrayList<String> 还是 LinkedList<String>?为什么?
  2. 你需要存储一个班级所有学生的学号 (String),要求不能重复,并且希望能够快速检查某个学号是否已经存在,对顺序没有要求。选择哪个 Set 实现?
  3. 你想记录每个单词 (String) 在一篇文章中出现的次数 (Integer)。选择哪个 Map 实现最合适?
  4. 新增:如果第3题中,你希望输出单词及其次数时,单词能按照字母顺序排列,应该选择哪个 Map 实现?
  5. 新增:你需要一个线程安全Map 来存储缓存数据,应该优先选择哪个类?

⭐ 工具类与遍历 ⭐

  1. 创建一个 ArrayList<Integer>,添加一些数字,然后使用 Collections.sort() 对其排序,并使用 Collections.reverse() 将其反转,最后打印结果。
  2. 使用迭代器遍历一个 HashSet<String>,并在遍历过程中安全地删除所有长度小于 5 的字符串。

⭐ 概念辨析 ⭐

  1. CollectionCollections 有什么区别?
  2. 为什么 HashMap 的 Key 需要正确重写 hashCode()equals() 方法?如果只重写 equals() 而不重写 hashCode() 会有什么问题?
  3. 新增ArrayListVector 有什么主要区别?为什么现在很少推荐使用 Vector

十、参考答案 ✅💡

⭐ 选择与应用答案 ⭐

  1. 选择 LinkedList

    • 原因:需要在列表开头进行频繁的添加操作。LinkedList首部添加元素的时间复杂度是 O(1),非常。而 ArrayList 在开头添加元素需要将所有现有元素向后移动,时间复杂度是 O(n),效率
  2. 选择 HashSet<String>

    • 原因:需求是去重 (Set 特性),快速检查是否存在 (HashSet 提供 O(1) 的 contains() 操作),且不关心顺序HashSet 完美符合这些要求。
  3. 选择 HashMap<String, Integer>

    • 原因:需要存储单词 (Key) 到出现次数 (Value) 的映射关系 (Map 功能)。单词 (Key) 是唯一的。通常查找某个单词的次数(get(word))和更新次数(put(word, count + 1))需要HashMap 提供 O(1) 的平均性能。对单词的存储顺序通常不关心。
  4. 选择 TreeMap<String, Integer>

    • 原因TreeMap 会根据 Key (String) 的自然顺序(字母顺序)自动对键值对进行排序
  5. 优先选择 ConcurrentHashMap<K, V> (来自 java.util.concurrent 包)。

    • 原因ConcurrentHashMap 提供了高效线程安全机制(如分段锁或 CAS),并发性能远好于使用全局锁的 HashtableCollections.synchronizedMap()

⭐ 工具类与遍历答案 ⭐

  1. 排序与反转 List:

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Arrays; // 方便初始化public class SortReverseDemo {public static void main(String[] args) {List<Integer> numbers = new ArrayList<>(Arrays.asList(5, 1, 8, 3, 2));System.out.println("Original list: " + numbers);Collections.sort(numbers); // 排序System.out.println("Sorted list:   " + numbers);Collections.reverse(numbers); // 反转System.out.println("Reversed list: " + numbers);}
    }
    
  2. 使用迭代器安全删除:

    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Set;public class IteratorRemoveDemo {public static void main(String[] args) {Set<String> words = new HashSet<>();words.add("Java");words.add("is");words.add("fun");words.add("and");words.add("powerful");System.out.println("Original set: " + words);// 必须使用迭代器进行遍历中的删除操作Iterator<String> iterator = words.iterator();while (iterator.hasNext()) {String word = iterator.next();if (word.length() < 5) {// 安全删除当前元素iterator.remove(); // 使用迭代器的 remove() 方法System.out.println("Removed: " + word);}}// 不能在增强 for 循环中直接调用 words.remove(word),会抛 ConcurrentModificationExceptionSystem.out.println("Set after removal: " + words);}
    }
    

⭐ 概念辨析答案 ⭐

  1. Collection vs Collections:

    • Collection: 是一个 接口 <📝>,单列集合
    • Collections: 是一个 工具类 <🛠️>,提供操作集合static方法。
  2. 为什么 HashMap Key 需要 hashCode()equals(): (答案同上一版本,此处省略)

  3. ArrayList vs Vector:

    • 主要区别: Vector线程安全的,它的所有方法都使用 synchronized 修饰ArrayList非线程安全的。此外,Vector 是 Java 早期 (JDK 1.0) 就存在的类,而 ArrayList 是在集合框架 (JDK 1.2) 中引入的。Vector默认扩容大小通常是翻倍,而 ArrayList 通常是增长 50%
    • 为什么少用 Vector: 因为其全局同步导致性能低下 <🐢>,在不需要线程安全的场景下完全没必要用它(用 ArrayList 更快)。而在需要线程安全的场景下,有性能更好的替代品,如使用 Collections.synchronizedList() 包装 ArrayList(虽然锁粒度仍然较大),或者直接使用 java.util.concurrent 包下的 CopyOnWriteArrayList(适用于读多写少的场景)。因此,Vector 现在基本被视为遗留类,在新代码中很少推荐使用

集合框架是 Java 中使用极其频繁的部分,掌握好它对日常开发至关重要!希望这篇笔记能帮你理清思路。如果觉得有帮助,别忘了 点赞👍、收藏⭐、关注 哦! 😉

相关文章:

  • VSCode开发调试Python入门实践(Windows10)
  • C#实现主流PLC读写工具类封装
  • 报错:函数或变量 ‘calcmie‘ 无法识别。
  • 【Linux】环境基础开发工具使用
  • 【AI提示词】成本效益分析师
  • Power BI企业运营分析——购物篮分析
  • 征服大海的人
  • Playwright MCP 入门实战:自动化测试与 Copilot 集成指南
  • python310 安装 tensorflow-gpu2.10
  • 【c语言】字符函数和字符串函数
  • PostgreSQL数据表操作SQL
  • python3GUI--视频监控管理平台 By:PyQt5(详细讲解)
  • Python-pandas-json格式的数据操作(读取数据/写入数据)
  • 天车定位技术方案(基于无线脉冲技术)
  • C++11新特性_范围-based for 循环
  • 长成一个西瓜
  • 【业务领域】电脑网卡是主板还是cpu(主板的网卡是什么意思)
  • Sigmoid函数导数推导详解
  • 拆解 browser-use 项目——深入理解 Agent 层
  • CUDA从入门到放弃
  • 五大白酒去年净利超1500亿元:贵州茅台862亿领跑,洋河营收净利齐降
  • 亚马逊一季度利润增超六成:云业务增速放缓,警告关税政策或影响业绩指引
  • 香港发生车祸致22人受伤,4人伤势严重
  • 长三角议事厅| AI作曲时代:长三角如何奏响数字音乐乐章
  • “非思”的思想——探索失语者的思想史
  • 女冰队长于柏巍,拒绝被年龄定义