当前位置: 代码网 > it编程>编程语言>Java > Java集合框架怎么选?List、Set、Map一看就懂

Java集合框架怎么选?List、Set、Map一看就懂

2026年07月19日 Java 我要评论
前言在程序开发中,我们经常需要保存和处理一组数据,例如购物车中的商品、文章标签、排队等待执行的任务、学生姓名与成绩的对应关系。数组虽然也能保存多个元素,但它的长度固定,增删元素不够灵活。java 集合

前言

在程序开发中,我们经常需要保存和处理一组数据,例如购物车中的商品、文章标签、排队等待执行的任务、学生姓名与成绩的对应关系。

数组虽然也能保存多个元素,但它的长度固定,增删元素不够灵活。java 集合框架就像一套准备好的“数据收纳工具箱”:不同的容器有不同的摆放规则,也适用于不同的业务场景。

java 集合框架主要分为两条路线:

  • collection:保存一个个独立元素,下面包括 listsetqueue
  • map:保存键值对,例如“用户名 → 用户信息”。

需要特别注意:map 属于集合框架的一部分,但它不是 collection 的子接口。

一、为什么需要集合框架

1.1 数组的局限

数组适合保存数量比较固定的数据:

string[] names = new string[3];
names[0] = "张三";
names[1] = "李四";
names[2] = "王五";

但是,当数据数量不断变化时,数组会显得不够灵活:

  • 数组长度创建后不能自动改变。
  • 中间插入或删除元素比较麻烦。
  • 查找、去重、排序等操作通常需要自己编写代码。

例如,一个购物车可能随时添加或删除商品;一个班级的签到名单也可能不断变化。这时,集合比数组更适合。

1.2 集合框架解决了什么问题

java 集合框架提供了统一的接口和常用实现类,使我们可以直接完成以下操作:

list.add("java");
list.remove("mysql");
set.contains("redis");
map.get("张三");
queue.poll();

可以把集合框架理解为不同用途的容器:

容器形象理解典型用途
list可重复的排号座位表购物车、学生名单
set不允许重复入场的名单标签去重、用户名检测
queue排队窗口打印任务、消息处理
deque两头都能进出的通道栈、撤销操作
map按编号查资料的柜子用户 id 查询、配置表

二、java 集合框架的整体结构

2.1 两条核心支线

java 集合框架的基础结构可以简化为:

iterable
   |
collection
   ├── list
   │     ├── arraylist
   │     └── linkedlist
   │
   ├── set
   │     ├── hashset
   │     ├── linkedhashset
   │     └── treeset
   │
   └── queue
         ├── priorityqueue
         └── deque
               ├── arraydeque
               └── linkedlist

map
   ├── hashmap
   ├── linkedhashmap
   └── treemap

其中:

  • list 关注元素的排列位置。
  • set 关注元素不能重复。
  • queue 关注元素按照某种规则依次处理。
  • map 关注通过唯一的键查找对应的值。

2.2 接口与实现类

实际开发中,通常使用接口声明变量,再选择具体实现类创建对象:

list<string> list = new arraylist<>();
set<string> set = new hashset<>();
queue<string> queue = new arraydeque<>();
map<string, integer> map = new hashmap<>();

这种写法可以理解为:

  1. 左边决定“我需要什么功能”
  2. 右边决定“底层使用什么结构实现”

例如:

list<string> list = new arraylist<>();

表示当前业务需要的是“列表”功能,至于底层先使用动态数组实现。以后需要更换实现类时,业务代码受到的影响会更小。

三、list:有序、可重复的列表

3.1 list 的特点

list 表示一个有顺序的集合,元素可以重复,并且可以通过下标访问。

list<string> courses = new arraylist<>();

courses.add("java");
courses.add("mysql");
courses.add("java");

system.out.println(courses);        // [java, mysql, java]
system.out.println(courses.get(1)); // mysql

这里第二次添加 "java" 不会被拦截,因为 list 允许重复元素。

适用场景:

  • 购物车商品列表。
  • 用户浏览记录。
  • 按顺序展示的文章评论。
  • 考试成绩列表。

list 官方定义强调了两个核心能力:元素具有顺序,并且可以按整数下标访问;它通常允许重复元素。(oracle docs)

3.2 arraylist:动态数组

arraylist 底层可以理解为一个能够自动扩容的数组。

list<string> cart = new arraylist<>();

cart.add("键盘");
cart.add("鼠标");
cart.add("显示器");

system.out.println(cart.get(1)); // 鼠标

它的特点如下:

操作特点
按下标查询快,适合频繁读取
尾部添加元素通常较快
中间插入或删除需要移动后面的元素
内存结构元素连续存放,额外开销较少

可以把 arraylist 想象成一排电影院座位。找到第 10 个座位很快,但如果要在中间增加一个座位,后面的人可能都要往后移动。

适用场景:

  • 查询操作多的列表。
  • 商品展示列表。
  • 查询结果集合。
  • 一般业务中的默认 list 实现。

arraylistlist 的可调整大小数组实现,并实现了随机访问能力。(oracle docs)

3.3 linkedlist:双向链表

linkedlist 底层由一个个相互连接的节点组成,每个节点知道前一个节点和后一个节点。

list<string> history = new linkedlist<>();

history.add("首页");
history.add("课程页");
history.add("订单页");

system.out.println(history);

它的特点如下:

操作特点
按下标查询需要沿节点寻找,效率较低
首尾添加、删除比较适合
中间插入、删除找到位置后修改连接关系较方便
内存结构每个节点需要保存前后引用

可以把 linkedlist 想象成一列手拉手的小火车。增加或移除一节车厢时,只需要调整相邻连接;但要直接找到第 100 节车厢,就需要一路数过去。

需要注意:不能简单理解为“linkedlist 任意位置增删都一定快”。如果需要先通过下标找到中间位置,寻找节点本身仍然需要遍历。

linkedlist 同时实现了 listdeque,按下标访问时会从头部或尾部开始遍历。(oracle docs)

四、set:元素不能重复的集合

4.1 set 的特点

set 的核心特点是:不允许保存重复元素

set<string> tags = new hashset<>();

tags.add("java");
tags.add("mysql");
tags.add("java");

system.out.println(tags.size()); // 2

第二次添加 "java" 时,集合不会重复保存它。

适用场景:

  • 对文章标签去重。
  • 判断用户名是否已经存在。
  • 统计不重复的访问用户。
  • 保存不能重复的编号。

需要特别注意:

set 的核心定义是“不重复”,而不是“所有实现类都无序”。

不同实现类对顺序的处理方式不同:

实现类是否去重顺序特点
hashset不保证遍历顺序
linkedhashset保留插入顺序
treeset按自然顺序或比较器排序

set 接口规定元素不能重复;hashset 不保证遍历顺序,而 linkedhashsettreeset 分别提供插入顺序和排序能力。(oracle docs)

4.2 hashset:快速去重

hashset 底层基于 hashmap 实现,适合快速判断某个元素是否存在。

set<string> uniquetags = new hashset<>();

uniquetags.add("java");
uniquetags.add("redis");
uniquetags.add("java");

system.out.println(uniquetags.contains("redis")); // true
system.out.println(uniquetags.size());            // 2

适用场景:

  • 单词去重。
  • 用户 id 去重。
  • 快速判断数据是否已经处理过。
list<string> words = arrays.aslist("java", "mysql", "java", "redis");
set<string> uniquewords = new hashset<>(words);

system.out.println(uniquewords);

当哈希分布良好时,hashsetaddremovecontainssize 等基本操作通常具有常数时间性能。(oracle docs)

4.3 linkedhashset:去重并保留原顺序

如果既想去重,又想保留元素首次出现的顺序,可以使用 linkedhashset

set<string> tags = new linkedhashset<>();

tags.add("java");
tags.add("mysql");
tags.add("java");
tags.add("redis");

system.out.println(tags); // [java, mysql, redis]

适用场景:

  • 对用户提交的数据去重,但保持展示顺序。
  • 对搜索关键词去重,同时保留原始输入顺序。

4.4 treeset:去重并排序

treeset 会在去重的同时,对元素进行排序。

set<integer> scores = new treeset<>();

scores.add(90);
scores.add(75);
scores.add(88);
scores.add(75);

system.out.println(scores); // [75, 88, 90]

适用场景:

  • 保存不重复且需要排序的分数。
  • 保存有序编号。
  • 查询某个范围内的数据。

treeset 基于 treemap,按照元素的自然顺序或指定比较器进行排序,其基础操作具有 o(log n) 的时间成本。(oracle docs)

五、queue 与 deque:等待处理的数据队列

5.1 queue:先进先出的排队结构

queue 表示队列,通常遵循先进先出原则,即先来的元素先处理。

queue<string> printqueue = new arraydeque<>();

printqueue.offer("文档a");
printqueue.offer("文档b");
printqueue.offer("文档c");

system.out.println(printqueue.poll()); // 文档a
system.out.println(printqueue.poll()); // 文档b

可以把队列理解为食堂排队:

入队:新同学站到队尾
出队:最前面的同学先打饭离开

适用场景:

  • 打印任务队列。
  • 消息消费队列。
  • 请求排队处理。
  • 广度优先搜索。

queue 常用方法有两组:

操作失败时抛异常失败时返回特殊值
插入元素add(e)offer(e)
删除队首remove()poll()
查看队首element()peek()

日常使用中,offer()poll()peek() 往往更安全,因为队列为空时不会直接抛出异常。queue 通常采用先进先出规则,但 priorityqueue 等实现类可以使用不同的出队规则。(oracle docs)

5.2 deque:两端都能操作的队列

deque 是双端队列,元素可以从头部加入或删除,也可以从尾部加入或删除。

deque<string> deque = new arraydeque<>();

deque.addfirst("b");
deque.addfirst("a");
deque.addlast("c");

system.out.println(deque); // [a, b, c]

deque 既可以模拟普通队列,也可以模拟栈。

作为队列使用

queue<string> queue = new arraydeque<>();

queue.offer("任务a");
queue.offer("任务b");

system.out.println(queue.poll()); // 任务a

作为栈使用

deque<string> stack = new arraydeque<>();

stack.push("首页");
stack.push("商品页");
stack.push("订单页");

system.out.println(stack.pop()); // 订单页
system.out.println(stack.pop()); // 商品页

适用场景:

  • 浏览器返回记录。
  • 编辑器撤销功能。
  • 深度优先搜索。
  • 普通业务队列。

虽然 arraydeque 底层使用数组实现,但它不是 list,不能通过 get(0) 这样的下标方式访问元素。官方文档还指出,arraydeque 用作栈时通常比 stack 更快,用作队列时通常比 linkedlist 更快。(oracle docs)

5.3 priorityqueue:优先处理重要任务

普通队列按照先来后到处理任务,而 priorityqueue 会按照优先级决定谁先出队。

queue<integer> queue = new priorityqueue<>();

queue.offer(80);
queue.offer(100);
queue.offer(60);

system.out.println(queue.poll()); // 60

默认情况下,数字越小,越先出队。

如果希望“优先级数字越大,任务越重要”,可以提供比较器:

queue<integer> queue = new priorityqueue<>(comparator.reverseorder());

queue.offer(80);
queue.offer(100);
queue.offer(60);

system.out.println(queue.poll()); // 100

适用场景:

  • 线上故障优先处理。
  • 医院急诊排队。
  • 考试成绩排名处理。
  • 定时任务调度。

需要注意:priorityqueue 只保证每次 poll() 取出的元素符合优先级规则,直接遍历整个队列时,不保证输出结果已经完全排好序。(oracle docs)

六、map:通过键查找值

6.1 map 的特点

map 保存的是键值对:

键 key  →  值 value

例如:

学号       → 学生姓名
商品编号   → 商品详情
用户名     → 登录信息
配置名称   → 配置值

示例:

map<string, integer> scores = new hashmap<>();

scores.put("张三", 90);
scores.put("李四", 86);

system.out.println(scores.get("张三")); // 90

map 中的键不能重复。相同的键再次调用 put() 时,新值会替换旧值:

scores.put("张三", 95);

system.out.println(scores.get("张三")); // 95

可以把 map 理解为快递柜:

柜门编号是 key
柜门里面的包裹是 value
同一个柜门编号只能对应一个当前位置上的包裹

map 的官方定义是键到值的映射,一个键最多只能映射到一个值。(oracle docs)

6.2 hashmap:最常用的键值容器

hashmap 是日常开发中非常常见的 map 实现。

map<string, string> users = new hashmap<>();

users.put("1001", "张三");
users.put("1002", "李四");

system.out.println(users.get("1001")); // 张三

适用场景:

  • 用户 id 查询用户信息。
  • 商品编号查询商品详情。
  • 单词出现次数统计。
  • 缓存临时数据。
map<string, integer> wordcount = new hashmap<>();

wordcount.put("java", 1);
wordcount.put("mysql", 1);
wordcount.put("java", wordcount.get("java") + 1);

system.out.println(wordcount.get("java")); // 2

hashmap 不保证遍历顺序;当哈希分布良好时,其 get()put() 等基本操作通常具有常数时间性能。(oracle docs)

6.3 linkedhashmap:按插入顺序遍历

如果希望键值对按照加入顺序输出,可以使用 linkedhashmap

map<string, integer> steps = new linkedhashmap<>();

steps.put("登录", 1);
steps.put("选择商品", 2);
steps.put("提交订单", 3);

system.out.println(steps.keyset()); // [登录, 选择商品, 提交订单]

适用场景:

  • 需要固定展示顺序的配置项。
  • 需要记录访问顺序的数据。
  • 需要实现简单 lru 缓存的场景。

linkedhashmap 在哈希表基础上维护了链表,因此拥有明确的遍历顺序,默认通常是插入顺序。(oracle docs)

6.4 treemap:按照键排序

treemap 会按照键的自然顺序或比较器规则进行排序。

map<integer, string> ranking = new treemap<>();

ranking.put(3, "铜牌");
ranking.put(1, "金牌");
ranking.put(2, "银牌");

system.out.println(ranking); // {1=金牌, 2=银牌, 3=铜牌}

适用场景:

  • 按编号排序的数据。
  • 按日期排序的记录。
  • 需要范围查询的数据。

treemap 基于红黑树实现,其 put()get()remove()containskey() 等操作具有 o(log n) 的时间成本。(oracle docs)

七、常用集合如何选择

7.1 选型思路

选择集合时,不要先背实现类,而应该先问业务需要什么能力:

业务需求推荐接口常用实现类
数据允许重复,并且需要下标访问listarraylist
数据允许重复,经常在首尾操作list / dequelinkedlist 或 arraydeque
数据不能重复,不关心顺序sethashset
数据不能重复,同时保留插入顺序setlinkedhashset
数据不能重复,同时自动排序settreeset
按先来后到处理任务queuearraydeque
按优先级处理任务queuepriorityqueue
根据唯一键快速查询值maphashmap
根据键查询值,并保留插入顺序maplinkedhashmap
根据键查询值,并按键排序maptreemap

7.2 最常见的默认选择

在普通业务开发中,可以先记住以下组合:

list<string> list = new arraylist<>();
set<string> set = new hashset<>();
queue<string> queue = new arraydeque<>();
map<string, string> map = new hashmap<>();

当业务明确提出“需要保持顺序”“需要自动排序”“需要优先级处理”等要求时,再更换为对应实现类。

八、测试代码

下面的完整代码演示以下内容:

  • arraylist 的增删改查与重复元素。
  • linkedlist 作为普通列表使用。
  • hashsetlinkedhashsettreeset 的区别。
  • arraydeque 作为队列和栈使用。
  • priorityqueue 按任务优先级出队。
  • hashmaplinkedhashmaptreemap 的区别。

代码兼容 jdk 8 及以上版本

import java.util.arraydeque;
import java.util.arraylist;
import java.util.arrays;
import java.util.comparator;
import java.util.deque;
import java.util.hashmap;
import java.util.hashset;
import java.util.linkedhashmap;
import java.util.linkedhashset;
import java.util.linkedlist;
import java.util.list;
import java.util.map;
import java.util.priorityqueue;
import java.util.queue;
import java.util.set;
import java.util.treemap;
import java.util.treeset;

/**
 * java 集合框架综合测试:jdk 8+ 可运行。
 */
public class collectionframeworkdemo {

    public static void main(string[] args) {
        testlist();
        testset();
        testqueueanddeque();
        testpriorityqueue();
        testmap();
    }

    /**
     * 测试 list:
     * 1. 元素有顺序
     * 2. 元素可以重复
     * 3. 可以通过下标访问和修改元素
     */
    private static void testlist() {
        system.out.println("===== 1. list:有序、可重复、可按下标访问 =====");

        list<string> shoppingcart = new arraylist<>();

        shoppingcart.add("java书");
        shoppingcart.add("键盘");
        shoppingcart.add("java书");       // list 允许重复
        shoppingcart.add(1, "鼠标");      // 在指定下标插入

        system.out.println("arraylist 初始内容:" + shoppingcart);
        system.out.println("下标为 1 的商品:" + shoppingcart.get(1));

        shoppingcart.set(1, "无线鼠标");  // 修改指定下标元素
        shoppingcart.remove("键盘");      // 删除指定元素

        system.out.println("修改和删除后:" + shoppingcart);

        list<string> recentpages = new linkedlist<>();
        recentpages.add("首页");
        recentpages.add("课程页");
        recentpages.add("订单页");

        system.out.println("linkedlist 作为普通列表:" + recentpages);
        system.out.println();
    }

    /**
     * 测试 set:
     * 1. 自动去重
     * 2. 不同实现类具有不同的顺序特点
     */
    private static void testset() {
        system.out.println("===== 2. set:元素不重复,具体实现决定顺序 =====");

        list<string> tags = arrays.aslist(
                "java", "mysql", "java", "redis", "mysql"
        );

        set<string> hashset = new hashset<>(tags);
        system.out.println("hashset 去重结果(顺序不保证):" + hashset);

        set<string> linkedhashset = new linkedhashset<>(tags);
        system.out.println("linkedhashset 去重并保留插入顺序:" + linkedhashset);

        set<string> treeset = new treeset<>(tags);
        system.out.println("treeset 去重并按自然顺序排序:" + treeset);

        system.out.println();
    }

    /**
     * 测试 queue 和 deque:
     * 1. queue 按先进先出处理元素
     * 2. deque 可以作为栈使用
     */
    private static void testqueueanddeque() {
        system.out.println("===== 3. queue / deque:排队处理与栈操作 =====");

        queue<string> printqueue = new arraydeque<>();

        printqueue.offer("文档a");
        printqueue.offer("文档b");
        printqueue.offer("文档c");

        system.out.println("队首但不移除 peek():" + printqueue.peek());

        while (!printqueue.isempty()) {
            system.out.println("打印完成:" + printqueue.poll());
        }

        system.out.println("空队列 poll() 返回:" + printqueue.poll());

        deque<string> browserbackstack = new arraydeque<>();

        browserbackstack.push("首页");
        browserbackstack.push("集合框架文章");
        browserbackstack.push("arraylist 文章");

        system.out.println("当前页面 peek():" + browserbackstack.peek());
        system.out.println("返回上一页,弹出:" + browserbackstack.pop());
        system.out.println("返回后所在页面:" + browserbackstack.peek());

        system.out.println();
    }

    /**
     * 测试 priorityqueue:
     * 数值越大的任务,优先级越高,越先处理。
     */
    private static void testpriorityqueue() {
        system.out.println("===== 4. priorityqueue:按优先级处理任务 =====");

        queue<task> tasks = new priorityqueue<>(
                comparator.comparingint(task::getpriority).reversed()
        );

        tasks.offer(new task("修复线上故障", 100));
        tasks.offer(new task("回复普通邮件", 10));
        tasks.offer(new task("发布版本", 80));

        while (!tasks.isempty()) {
            system.out.println("处理任务:" + tasks.poll());
        }

        system.out.println();
    }

    /**
     * 测试 map:
     * 1. key 不能重复
     * 2. 相同 key 再次 put 会覆盖旧 value
     * 3. 不同 map 实现类的顺序特点不同
     */
    private static void testmap() {
        system.out.println("===== 5. map:通过唯一键查找值 =====");

        map<string, integer> scoremap = new hashmap<>();

        scoremap.put("张三", 90);
        scoremap.put("李四", 86);
        scoremap.put("张三", 95); // 相同 key 会替换旧 value

        system.out.println("张三最新成绩:" + scoremap.get("张三"));
        system.out.println("王五成绩,不存在时给默认值:"
                + scoremap.getordefault("王五", 0));

        for (map.entry<string, integer> entry : scoremap.entryset()) {
            system.out.println(
                    "学生=" + entry.getkey()
                            + ",成绩=" + entry.getvalue()
            );
        }

        map<string, integer> insertionordered = new linkedhashmap<>();

        insertionordered.put("first", 1);
        insertionordered.put("second", 2);
        insertionordered.put("third", 3);

        system.out.println("linkedhashmap 插入顺序:"
                + insertionordered.keyset());

        map<integer, string> ranking = new treemap<>();

        ranking.put(3, "铜牌");
        ranking.put(1, "金牌");
        ranking.put(2, "银牌");

        system.out.println("treemap 按 key 排序:" + ranking);
    }

    /**
     * 模拟一个待处理任务。
     */
    private static class task {
        private final string name;
        private final int priority;

        private task(string name, int priority) {
            this.name = name;
            this.priority = priority;
        }

        private int getpriority() {
            return priority;
        }

        @override
        public string tostring() {
            return name + "(优先级=" + priority + ")";
        }
    }
}

运行后可以观察到:

list 中可以同时保存两个“java书”
set 会自动去除重复标签
queue 会按照 文档a → 文档b → 文档c 的顺序处理
deque 作为栈时,最后访问的页面最先弹出
priorityqueue 会先处理“修复线上故障”
map 中张三的成绩会从 90 被更新为 95
linkedhashmap 会保持 first、second、third 的插入顺序
treemap 会按照 1、2、3 的键顺序输出

需要注意:

hashset 和 hashmap 的遍历输出顺序不固定,
不要在代码中依赖它们的打印顺序。

九、常见易混知识点

9.1 collection 与 collections 不一样

collection 是集合接口体系的根接口之一:

collection<string> collection;

collections 是工具类,提供排序、反转、查找最大值等静态方法:

collections.sort(list);
collections.reverse(list);
collections.max(list);

可以简单记忆为:

collection  是容器体系
collections 是操作容器的工具箱

9.2 map 不属于 collection 子接口

虽然 map 也属于 java 集合框架,但它保存的是键值对,而 collection 保存的是单个元素。

list<string> list = new arraylist<>();
map<string, integer> map = new hashmap<>();

它们属于两条不同的体系。

总结

java 集合框架的核心不是记住大量类名,而是理解不同容器解决的问题:

  • list:保存有顺序、允许重复的数据。
  • set:保存不能重复的数据。
  • queue:保存等待处理的数据。
  • deque:支持两端操作,也可以模拟栈。
  • map:通过唯一键快速查找对应值。

在常见业务开发中,可以优先掌握:

arraylist
hashset
arraydeque
hashmap

当业务进一步要求保持插入顺序、自动排序或优先级处理时,再选择:

linkedhashset
treeset
priorityqueue
linkedhashmap
treemap

理解“需要什么能力,再选择什么容器”,比单纯背诵集合类更加重要。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com