Day22 - 集合 - 1.Collection集合

1.Collection集合

1.1数组和集合的区别【理解】

  • 相同点

    都是容器,可以存储多个数据

  • 不同点

    • 数组的长度是不可变的,集合的长度是可变的

    • 数组可以存基本数据类型和引用数据类型

      集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类

 

新增补充:

  • 从底层机制来看:

    • 数组分配的内存是连续的,查询效率高(O(1)),但插入/删除时需要整体移动数据,效率低。

    • 集合底层是基于链表/动态数组/哈希表等结构,扩容机制会自动调整容量,插入/删除灵活,但查询效率视具体实现而定(如 ArrayList 查询快、LinkedList 插入快)。

  • 实战应用场景对比:

    • 固定容量、大批量数据场景优先考虑数组(如高性能计算、内存池)。

    • 数据规模不确定、频繁增删操作时优先选择集合。

🧠 理论理解

数组和集合本质上都是数据容器,但设计初衷不同:

  • 数组是静态数据结构,长度固定,适合存储已知数量的元素。它可以存储基本数据类型(如 int[])或引用数据类型(如 String[]),但一旦初始化,容量不可变。

  • 集合是动态数据结构,容量可动态扩展,仅支持存储对象(引用类型),存储基本类型时需要使用包装类(如 Integer 替代 int)。集合提供了丰富的API,支持动态增删查改。

此外,数组在内存中是连续存储,而集合的底层实现(如链表、哈希表)可能是非连续存储,导致在查找、插入、删除上的性能特点也不同。

🏢 企业实战理解

  • 阿里巴巴:在支付系统的订单号生成中,为了极致性能使用数组批量缓存预生成的流水号,但在订单历史存储、查询中使用集合(如 ListSet)。

  • 字节跳动:推荐系统中,算法层返回的初步推荐结果以数组存储(快速索引),但后续过滤、打分流程中基于 ArrayList 动态操作数据。

  • Google:在大规模分布式计算(如 MapReduce)中,内部中间数据采用集合结构管理任务列表,实现灵活扩展和动态调度。

面试题 1:
阿里巴巴面试:请详细说说数组和集合的区别?实际开发中如何选择使用?

参考答案:
数组是 Java 中一种基础的数据结构,特点是长度固定支持基本类型和引用类型,其优势是存取速度快(内存连续)。适用于数据量已知且结构简单的场景,比如频繁进行索引访问的场景(如缓存池)。

集合是 JDK 提供的高级数据结构,容量可动态扩展,主要操作对象是对象引用。集合只能存储引用类型,若要存储基本类型(如 int),需要使用包装类(如 Integer)。集合内置了丰富的增删查改 API,灵活性远大于数组。

如何选择:

  • 如果数据规模固定、操作简单,数组优先,比如处理大量数字的算法题。

  • 如果数据量不确定、需要动态变化、频繁插入删除,集合优先。比如用户在线列表、评论区动态加载等。

大厂加分点:
字节跳动在高性能推荐场景中,常用数组处理打分排序(追求极致性能),而在业务数据流转(如推荐列表)中用 ArrayList 灵活应对数据变动。

 

场景题 1:
你在字节跳动负责短视频推荐系统优化,有一个模块需要存储推荐池里的 500 个视频对象,推荐逻辑实时刷新池子(添加/删除)。技术负责人问你:这里是用数组好,还是用集合好?为什么?

参考答案:
在推荐池中,虽然 500 条视频听起来是定量的,但因为实时刷新涉及频繁增删,所以集合更合适。

数组的特点是长度固定,一旦初始化 500 长度就无法扩容或缩减。如果采用数组,后续的增删都要手动维护新数组,复制旧数据,非常低效。

集合(如 ArrayList)的优势在于动态扩容和提供完善的 add()remove() 等操作方法,能高效应对池子数据的增删变化。

实战中,字节跳动推荐系统使用 ArrayList 动态更新推荐池,而在短时间快照生成时,用数组复制集合(list.toArray()),加速内存访问。

 

 

1.2集合类体系结构【理解】

 

现有结构图很好,这里补上几点你未提到的知识:

  • Collection顶层接口 是单列集合的根接口(存储单个元素),Map则是双列集合的根接口(存储键值对)。

  • 主要子接口:

    • List:有序、可重复(如 ArrayListLinkedList

    • Set:无序、不重复(如 HashSetTreeSet

    • Queue/Deque:队列/双端队列(如 LinkedList 实现了 Deque

  • 补充说明:

    • JDK 1.5 之后引入了泛型机制,使集合在编译时就能检查类型安全。

    • JDK 1.8 之后 Collection 接口增加了 default 方法,如 forEach 等,支持函数式编程。

 

🧠 理论理解

Java 集合体系基于 CollectionMap 两大顶层接口:

  • Collection → 存储单列数据(元素):

    • List:有序、可重复(如 ArrayListLinkedList

    • Set:无序、不重复(如 HashSetTreeSet

    • Queue:队列/双端队列(如 LinkedListPriorityQueue

  • Map → 存储双列数据(键值对):

    • HashMapTreeMap 等。

设计采用了接口+实现类分层思想,提供多样化选择,同时鼓励面向接口编程。

🏢 企业实战理解

  • 美团:在外卖派单系统中,订单对象存储在 PriorityQueue 中,实现优先级调度。

  • 字节跳动:微服务网关将在线用户会话用 ConcurrentHashMap 存储,实现高并发读写的线程安全。

  • 英伟达:GPU任务调度中,通过 TreeSet 管理任务优先级,实现任务的有序执行。

面试题 2:
腾讯面试:Java 的集合体系结构是如何设计的?为什么这么设计?

参考答案:
Java 的集合体系结构基于两个大接口:

1️⃣ Collection 接口(单列数据):

  • List(有序可重复,如 ArrayListLinkedList

  • Set(无序不可重复,如 HashSetTreeSet

  • Queue(队列/优先队列,如 LinkedListPriorityQueue

2️⃣ Map 接口(双列数据):

  • HashMapTreeMap

这种设计是典型的接口+实现类分层

  • 接口定义标准操作(增、删、查、遍历)

  • 实现类提供不同的底层实现,适应不同场景(数组结构、链表结构、哈希结构、红黑树等)

设计好处:

  • 符合面向接口编程,解耦性强

  • 不同实现类可以自由切换,提升灵活性。

  • 开闭原则(对修改关闭、对扩展开放),方便维护。

实战场景补充:
Google 在大规模数据调度中,会根据性能需求切换 HashSet(哈希结构)和 TreeSet(有序红黑树结构),保证数据唯一性同时满足有序性要求。

 

场景题 2:
腾讯 QQ 群功能中,有“群成员列表”和“已读消息列表”这两部分。你负责优化底层数据结构,请问分别该用什么集合?为什么?

参考答案:

  • 群成员列表:需要保证成员不重复,且支持快速查找,适合使用 HashSetLinkedHashSet。如果还需要保证列表顺序(按加入顺序排列),推荐 LinkedHashSet

  • 已读消息列表:涉及消息的发送时间顺序、重复允许(有些消息可能重复),适合使用 ArrayList,它支持顺序访问、快速遍历。

腾讯在 IM 消息模块中,大规模使用 Set 确保群成员的唯一性,并利用 List 记录聊天记录流,保持顺序处理。

 

1.3Collection 集合概述和使用【应用】

  • Collection集合概述

    • 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素

    • JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现

  • 创建Collection集合的对象

    • 多态的方式

    • 具体的实现类ArrayList

  • Collection集合常用方法

    方法名说明
    boolean add(E e)添加元素
    boolean remove(Object o)从集合中移除指定的元素
    boolean removeIf(Object o)根据条件进行移除
    void clear()清空集合中的元素
    boolean contains(Object o)判断集合中是否存在指定的元素
    boolean isEmpty()判断集合是否为空
    int size()集合的长度,也就是集合中元素的个数

除了已有的 add、remove 方法,这里我补充几个 实际开发中常用且易忽略的点

  • containsAll(Collection c):判断当前集合是否完全包含另一个集合的所有元素。

  • addAll(Collection c):一次性向集合中添加另一个集合的所有元素。

  • retainAll(Collection c):取两个集合的交集。

  • removeAll(Collection c):移除当前集合中与另一个集合相同的元素。

扩展:

  • 线程安全问题Collection 及其子类如 ArrayList 默认是非线程安全的,若多线程环境中使用需要:

    • 使用 Collections.synchronizedCollection 包装;

    • 或直接用 CopyOnWriteArrayListVector 等线程安全实现。

  • 性能对比

    • ArrayList:随机访问快,插入删除慢(尤其中间位置)。

    • LinkedList:插入删除快,随机访问慢。

 

🧠 理论理解

Collection 是所有单列集合的根接口,核心设计理念是抽象出集合的基本操作

  • 增(add

  • 删(remove

  • 查(contains

  • 遍历(iteratorforEach

ArrayList 是最常用的实现类,它基于动态数组实现,可随机访问,扩容时会重新分配更大的数组并拷贝数据。

🏢 企业实战理解

  • 腾讯:直播弹幕的缓存列表使用 ArrayList 存储,当弹幕量达到上限时清空或持久化。

  • OpenAI:在对话历史管理中,将每轮对话记录动态存储到 ArrayList 中,便于序列化成上下文传递给大模型。

面试题 3:
字节跳动面试:谈谈 Collection 集合的核心方法,并结合实际举例说明。

参考答案:
Collection 接口核心方法包括:

  • add(E e):添加元素

  • remove(Object o):移除元素

  • clear():清空集合

  • contains(Object o):判断是否存在

  • size():获取集合长度

  • isEmpty():判断是否为空

比如,在抖音短视频推荐中,一个推荐列表(ArrayList)会先 add 用户匹配的视频,再 remove 违规或重复的视频。上线前,会用 clear() 清空缓存,避免历史数据干扰。

面试细节:

  • remove(Object o) 是按对象值删除,不是按索引删除。

  • contains() 的底层依赖 equals() 方法,若自定义对象,务必重写 equals()hashCode() 保证判断正确。

实战中,腾讯的 IM 消息系统在处理用户会话列表时,频繁使用 Collection 方法动态增删在线好友。

 

场景题 3:
你在阿里巴巴负责电商促销系统,项目中有个“用户限购名单”功能,要求实时更新违规名单(比如下单过多的用户),后台还需要快速判断某用户是否已在限购名单中。你选什么数据结构?如何实现?

参考答案:
“限购名单”需要:
1️⃣ 实时更新(增删)
2️⃣ 快速判断是否存在

推荐使用 HashSet

  • add()remove() 操作时间复杂度 O(1)

  • contains() 判断是否存在的速度极快

实战中,阿里在“双 11”场景下实时更新违规名单,通过 HashSet 存储黑名单,再结合 contains() 快速校验下单用户是否合法,大幅降低数据库压力。

 

1.4Collection集合的遍历

1.4.1 迭代器遍历
  • 迭代器介绍

    • 迭代器,集合的专用遍历方式

    • Iterator<E> iterator(): 返回此集合中元素的迭代器,通过集合对象的iterator()方法得到

  • Iterator中的常用方法

    boolean hasNext(): 判断当前位置是否有元素可以被取出 E next(): 获取当前位置的元素,将迭代器对象移向下一个索引位置

  • Collection集合的遍历

    public class IteratorDemo1 {
        public static void main(String[] args) {
            //创建集合对象
            Collection<String> c = new ArrayList<>();
    ​
            //添加元素
            c.add("hello");
            c.add("world");
            c.add("java");
            c.add("javaee");
    ​
            //Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
            Iterator<String> it = c.iterator();
    ​
            //用while循环改进元素的判断和获取
            while (it.hasNext()) {
                String s = it.next();
                System.out.println(s);
            }
        }
    }

  • 迭代器中删除的方法

    void remove(): 删除迭代器对象当前指向的元素

    public class IteratorDemo2 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("b");
            list.add("c");
            list.add("d");
    ​
            Iterator<String> it = list.iterator();
            while(it.hasNext()){
                String s = it.next();
                if("b".equals(s)){
                    //指向谁,那么此时就删除谁.
                    it.remove();
                }
            }
            System.out.println(list);
        }
    }

🧠 理论理解

Iterator 提供了通用遍历接口,实现了“游标式”遍历。核心方法:

  • hasNext():判断是否有下一个元素。

  • next():返回下一个元素并移动游标。

  • remove():删除当前元素(迭代器安全删除)。

fail-fast机制:如果在迭代过程中直接使用集合修改结构,会抛出 ConcurrentModificationException

🏢 企业实战理解

  • 字节跳动:在推荐系统数据过滤中,采用迭代器遍历大集合数据并删除不符合条件的元素,避免 ConcurrentModificationException

  • 美团:骑手位置实时更新时,用迭代器安全遍历并剔除已超时的位置信息,保持数据准确性。

面试题 4:
阿里巴巴面试:迭代器的底层工作原理是什么?如何保证遍历时删除元素的安全性?

参考答案:
Iterator 遍历底层是基于游标的,三步走:
1️⃣ hasNext() 判断是否有下一个元素;
2️⃣ next() 获取元素并把游标往后移动;
3️⃣ 内部维护一个 expectedModCount 防止并发修改异常(fail-fast)

当遍历时如果直接调用集合的 remove() 方法修改集合,modCount 改变,而 expectedModCount 没更新,触发 ConcurrentModificationException

正确做法: 使用 Iterator.remove() 方法删除当前元素,它会同步更新 expectedModCount,确保安全。

实战中,美团的骑手列表动态剔除离线骑手时,必须用 Iterator.remove(),避免线程间意外报错。

 

场景题 4:
你在 Google 参与翻译产品开发,翻译历史记录是一个 ArrayList 存储的。当你需要遍历所有历史记录,并删除含有敏感词的记录,如何做才能保证安全不报错?

参考答案:
因为涉及遍历中删除,必须使用 Iterator 提供的 remove() 方法。传统用 list.remove() 会导致 ConcurrentModificationException

做法:

Iterator<String> it = historyList.iterator();
while (it.hasNext()) {
    String record = it.next();
    if (record.contains("敏感词")) {
        it.remove();
    }
}

Google 翻译服务中,历史记录同步更新模块采用 Iterator.remove() 安全删除违规内容,确保不会触发异常影响体验。

 

1.4.2 增强for
  • 介绍

    • 它是JDK5之后出现的,其内部原理是一个Iterator迭代器

    • 实现Iterable接口的类才可以使用迭代器和增强for

    • 简化数组和Collection集合的遍历

  • 格式

    for(集合/数组中元素的数据类型 变量名 : 集合/数组名) {
    
    // 已经将当前遍历到的元素封装到变量中了,直接使用变量即可
    
    }

  • 代码

    public class MyCollectonDemo1 {
        public static void main(String[] args) {
            ArrayList<String> list =  new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");
            list.add("f");
    ​
            //1,数据类型一定是集合或者数组中元素的类型
            //2,str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素
            //3,list就是要遍历的集合或者数组
            for(String str : list){
                System.out.println(str);
            }
        }
    }

  • 细节点注意:

1.报错NoSuchElementException

2.迭代器遍历完毕,指针不会复位

3.循环中只能用一次next方法

4.迭代器遍历时,不能用集合的方法进行增加或者删除

 

🧠 理论理解

增强for是 Iterator 的语法糖,简化了遍历逻辑,但无法在循环中安全修改集合结构

本质:

for (String s : list) {
    // 底层其实是使用了Iterator
}

🏢 企业实战理解

  • 阿里巴巴:日志系统定时遍历缓存区日志列表,采用增强for直接输出所有日志数据。

  • 谷歌:在Android系统中,增强for广泛用于遍历配置参数列表,提高代码可读性。

 

面试题 5:
字节跳动面试:增强 for 和 Iterator 有什么关系?是否能在增强 for 里删除元素?

参考答案:
增强 for 是 Iterator语法糖,底层其实调用 iterator() 方法遍历集合。增强 for 简化了代码,但限制较多:

  • 不能安全删除元素,因为它没有暴露 remove() 方法。

  • 只能顺序单向遍历,不能像 ListIterator 支持双向遍历。

如果想删除,必须用显式 Iterator

字节跳动在用户黑名单数据更新中,禁止用增强 for 删除数据,统一用迭代器实现安全修改

public class A04_CollectionDemo4 {
    public static void main(String[] args) {
      /*
        迭代器的细节注意点:
            1.报错NoSuchElementException
            2.迭代器遍历完毕,指针不会复位
            3.循环中只能用一次next方法
            4.迭代器遍历时,不能用集合的方法进行增加或者删除
                暂时当做一个结论先行记忆,在今天我们会讲解源码详细的再来分析。
                如果我实在要删除:那么可以用迭代器提供的remove方法进行删除。
                如果我要添加,暂时没有办法。(只是暂时)
       */
​
        //1.创建集合并添加元素
        Collection<String> coll = new ArrayList<>();
        coll.add("aaa");
        coll.add("bbb");
        coll.add("ccc");
        coll.add("ddd");
​
        //2.获取迭代器对象
        //迭代器就好比是一个箭头,默认指向集合的0索引处
        Iterator<String> it = coll.iterator();
        //3.利用循环不断的去获取集合中的每一个元素
        while(it.hasNext()){
            //4.next方法的两件事情:获取元素并移动指针
            String str = it.next();
            System.out.println(str);
        }
​
        //当上面循环结束之后,迭代器的指针已经指向了最后没有元素的位置
        //System.out.println(it.next());//NoSuchElementException
​
        //迭代器遍历完毕,指针不会复位
        System.out.println(it.hasNext());
​
        //如果我们要继续第二次遍历集合,只能再次获取一个新的迭代器对象
        Iterator<String> it2 = coll.iterator();
        while(it2.hasNext()){
            String str = it2.next();
            System.out.println(str);
        }
    }
}
  • fail-fast机制

    • 当使用 Iterator 遍历时,如果在遍历过程中直接用集合自身的 addremove 修改集合,会触发 ConcurrentModificationException

    • 原因:迭代器维护了一个 modCount 版本号,一旦检测到集合结构被修改且不是通过 iterator.remove(),就会报错。

  • 源码核心:

    • ArrayList 内部的 Itr 类,hasNext() 判断是否越界,next() 返回元素并移动游标。

 

补充细节:

  • 增强for是 Iterator 的语法糖,不能在遍历中修改集合结构,否则同样会触发 ConcurrentModificationException

  • 增强for适用于读取场景,不适用于修改/删除场景

场景题 5:
美团外卖系统中有个“骑手列表”,你需要遍历所有骑手并输出状态日志。因为仅是遍历输出,不涉及删除,你选用什么方式?为什么?

参考答案:
因为只是单纯遍历输出,推荐使用增强 for

for (Rider rider : riderList) {
    System.out.println(rider.getName() + " 状态:" + rider.getStatus());
}

增强 for 简洁、可读性好,底层使用迭代器实现,非常适合“只读”场景。美团外卖系统中实时监控日志模块,广泛使用增强 for 进行状态遍历。

 

1.4.3 lambda表达式

利用forEach方法,再结合lambda表达式的方式进行遍历

public class A07_CollectionDemo7 {
    public static void main(String[] args) {
       /* 
        lambda表达式遍历:
                default void forEach(Consumer<? super T> action):
        */
​
        //1.创建集合并添加元素
        Collection<String> coll = new ArrayList<>();
        coll.add("zhangsan");
        coll.add("lisi");
        coll.add("wangwu");
        //2.利用匿名内部类的形式
        //底层原理:
        //其实也会自己遍历集合,依次得到每一个元素
        //把得到的每一个元素,传递给下面的accept方法
        //s依次表示集合中的每一个数据
       /* coll.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });*/
​
        //lambda表达式
        coll.forEach(s -> System.out.println(s));
    }
}

🧠 理论理解

JDK 1.8 开始,Collection 提供 forEach 默认方法,结合 lambda 表达式,实现函数式遍历,提高了代码简洁性:

list.forEach(s -> System.out.println(s));

底层本质是内部迭代,可配合 Stream 提供链式操作。

🏢 企业实战理解

  • OpenAI:在对话接口中,批量处理用户输入数据,结合 forEach 实现异步流式处理,提升响应效率。

  • 字节跳动:推荐系统中用 lambda + Stream 链式处理多级过滤、打分操作,实现高效并行计算。

面试题 6:
OpenAI 面试:你如何看待 lambda 表达式结合 forEach 的使用?它适合什么场景?是否存在限制?

参考答案:
Lambda+forEach 提供了更现代化的遍历方式,代码简洁、可读性强:

list.forEach(s -> System.out.println(s));

适合场景:

  • 对集合进行只读操作(如打印、统计)。

  • 配合 Stream 使用,实现链式处理(如过滤、映射)。

限制:

  • 不能像 Iterator 那样删除元素(UnsupportedOperationException)。

  • 内部迭代不可中断,无法像传统 for 循环中使用 breakcontinue 控制流程。

OpenAI 在模型批量训练任务中,用 lambda+forEach 快速分发数据包,实现并行分片。

 

场景题 6:
你在英伟达 GPU 云平台开发日志收集模块,需要高效遍历 GPU 节点列表,并实时打印心跳包状态。如何简化代码?

参考答案:
推荐使用 lambda + forEach:

gpuNodeList.forEach(node -> System.out.println(
    node.getId() + " 心跳状态:" + node.getHeartbeat()));

这种写法简洁明了,特别适合日志场景。英伟达在 GPU 云调度模块中,使用 lambda 结合 forEach 高效实现节点状态监控和日志收集。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏驰和徐策

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值