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

【Java 集合】核心知识点梳理

Java 集合框架核心知识点梳理

一、集合框架整体概览

Java 集合是用于存储、管理和操作一组对象的容器,核心作用是替代数组的局限性,提供更灵活、高效的数据存储方案。

1. 集合与数组的核心区别

特性数组集合
长度特性固定长度,创建后无法修改动态长度,支持自动扩容 / 缩容
存储类型可存储基本数据类型(int、long 等)或对象仅存储对象(基本类型需自动装箱为包装类)
访问方式直接通过下标访问(O (1) 时间复杂度)需通过迭代器、遍历或键(Map)访问
功能丰富度仅提供长度属性,无额外操作方法内置增删改查、排序、过滤等丰富方法(如add()remove()contains()
适用场景存储固定数量、类型统一的数据存储数量不确定、需频繁操作(增删改查)的数据

2. 集合框架体系结构

Java 集合框架核心分为两大体系,基于CollectionMap接口:
在这里插入图片描述

3. 常用集合总结

日常开发中高频使用的集合:

  • List:ArrayList(查询优先)、LinkedList(增删优先);
  • Set:HashSet(去重 + 高效查询)、LinkedHashSet(去重 + 有序);
  • Map:HashMap(键值对存储,高频首选)、LinkedHashMap(键值对 + 有序)、ConcurrentHashMap(多线程场景)。

二、List 接口核心实现类

List 接口的核心特点是有序(元素插入顺序与遍历顺序一致)、可重复,重点掌握 ArrayList 和 LinkedList。

1. ArrayList(动态数组)

(1)底层原理
  • 底层基于Object[]数组实现,默认初始容量为 10;
  • 实现List接口,支持随机访问(通过下标直接获取元素),查询效率极高;
  • 非线程安全,多线程环境需手动同步(如Collections.synchronizedList())。
(2)扩容机制
  • 触发条件:当元素数量(size)超过当前数组容量时,触发扩容;
  • 扩容公式:新容量 = 旧容量 + 旧容量右移 1 位(等价于newCapacity = oldCapacity * 1.5);
    • 例:初始容量 10,插入第 11 个元素时,扩容为 15;再满则扩容为 22(15*1.5);
  • 扩容流程:创建新容量的数组 → 复制旧数组元素到新数组 → 指向新数组(时间复杂度 O (n),扩容频繁会影响性能)。
(3)面试重点:为什么扩容因子是 1.5?
  1. 平衡时间与空间效率:
    • 扩容因子过小(如 1.2):频繁扩容,复制元素的开销增大,时间复杂度退化;
    • 扩容因子过大(如 2.0):空间浪费严重(可能有大量空闲数组位置);
  2. 高效计算:1.5 可通过 “右移 1 位 + 加法” 实现(oldCapacity + (oldCapacity >> 1)),避免浮点数运算,提升执行效率;
  3. 内存利用率:基于统计规律,1.5 能保证数组的空闲率适中,减少内存碎片。
(4)核心性能特点
  • 优势:随机访问(get(int index))效率高(O (1));
  • 劣势:增删元素效率低(尤其是中间位置)—— 需移动数组元素(O (n))。

2. LinkedList(双向链表)

(1)底层原理
  • 底层基于双向链表实现,每个节点(Node)包含prev(前驱指针)、item(元素)、next(后继指针);
  • 实现ListQueue接口,支持队列的先进先出(FIFO)操作;
  • 非线程安全。
(2)核心性能特点
  • 优势:头尾增删元素(addFirst()addLast()removeFirst())效率高(O (1));
  • 劣势:随机访问效率低(O (n))—— 需从链表头 / 尾遍历到目标位置。

3. ArrayList vs LinkedList 对比(面试高频)

对比维度ArrayListLinkedList
底层结构动态数组双向链表
随机访问O (1)(高效)O (n)(低效)
中间增删O (n)(需移动元素)O (n)(需遍历找到位置)
头尾增删O (n)(需扩容 / 移动元素)O (1)(高效)
内存占用连续内存,少量空闲空间(扩容预留)非连续内存,每个节点额外存储指针(内存开销略大)
适用场景查询频繁、增删少的场景(如数据展示)增删频繁(尤其是头尾)、查询少的场景(如队列、栈)

三、Map 接口核心实现类

Map 接口的核心特点是存储键值对 <K,V>、键唯一(重复键会覆盖值)、值可重复,重点掌握 HashMap、LinkedHashMap 和 ConcurrentHashMap

1. HashMap(核心重点)

(1)底层原理(JDK1.7 vs JDK1.8)
JDK 版本底层结构核心差异
1.7数组 + 链表扩容时采用头插法,多线程下易产生环形链表(死循环)
1.8数组 + 链表 + 红黑树扩容时采用尾插法,解决死循环;链表长度≥8 且数组长度≥64 时,链表转为红黑树(查询效率从 O (n)→O (logn))
(2)核心参数
  • 默认初始容量:16(必须是 2 的次幂);
  • 负载因子:0.75(阈值 = 容量 × 负载因子,默认初始阈值 = 16×0.75=12);
  • 树化阈值:链表长度≥8;
  • 反树化阈值:红黑树节点数≤6(还原为链表,降低树维护成本)。
(3)put () 方法核心流程(JDK1.8)

在这里插入图片描述

  1. 哈希计算与槽位定位 :
    • 扰动计算:h = key.hashCode() ^ (h >>> 16)(将哈希值高位特征融入低位,减少冲突);
    • 若数组未初始化,调用resize()初始化(默认容量 16);
    • 槽位计算:index = (n-1) & h(n 为数组长度,2 的次幂保证 n-1 二进制全 1,让哈希值充分参与运算)。
  2. 处理目标桶元素:
    • 桶为空:直接新建 Node 节点存入,modCount(修改次数)和size递增,进入扩容检查;
    • 桶非空(哈希冲突):
      • 检查桶中第一个节点:若 key 的 hash 和 equals 匹配,标记为待更新节点;
      • 不匹配则分情况:
        • 节点是 TreeNode(已树化):调用putTreeVal()在红黑树中查找 / 新增节点;
        • 节点是普通链表:遍历链表,找到匹配节点则标记更新,否则新增节点挂在链尾,modCountsize递增。
  3. 树化检查:新增链表节点后,若链表长度≥8 且数组长度≥64,调用treeifyBin()转为红黑树(数组长度不足 64 则优先扩容)。
  4. 更新操作:若找到匹配节点,用新 value 覆盖旧 value,返回旧 value(不触发扩容)。
  5. 扩容检查:若为新增节点,检查size是否超过阈值,超过则调用resize()扩容。
(4)扩容机制(核心面试点)
  1. 触发条件size > 容量×负载因子(新增元素后检查);
  2. 扩容核心流程:
    • 新容量 = 旧容量 ×2(保持 2 的次幂),新阈值 = 新容量 ×0.75;
    • 新建 2 倍长度的数组,迁移旧数组中所有节点:
      • 无需重新计算 hash,仅判断原哈希值中 “旧容量对应的 bit 位”:
        • 该 bit 为 0:新索引 = 原索引;
        • 该 bit 为 1:新索引 = 原索引 + 旧容量;
      • 红黑树迁移后,若节点数≤6 则还原为链表。
  3. 为什么扩容是 2 的次幂?
    • 保证(n-1) & h运算等价于 “哈希值对 n 取模”,且运算效率更高(位运算比取模快);
    • n 为 2 的次幂时,n-1 二进制全 1,能让哈希值的每一位都参与运算,降低哈希冲突概率。
(5)哈希冲突相关
  • 定义:不同 key 的哈希值经计算后映射到同一个桶(数组下标),即为哈希冲突;
  • 产生原因:哈希函数的输出空间有限(数组容量固定),而输入的 key 是无限的,必然存在 “多对一” 映射;
  • 解决方法:
    1. 拉链法(HashMap 采用):每个桶维护一个链表 / 红黑树,冲突元素存入链表 / 树中;
    2. 再哈希法:使用多个哈希函数,若第一个函数冲突则用第二个计算;
    3. 扩容法:扩大数组容量,重新分配节点,减少冲突概率。
(6)多线程安全问题(JDK1.7 vs JDK1.8)

在这里插入图片描述
JDK1.7,多线程背景下,在数组扩容的时候,存在 Entry 链死循环和数据丢失问题,因为resize() 时使用头插法将旧链表节点迁移到新数组。

  • JDK 1.7:并发扩容 → 头插法 → 环形链表 → 死循环
  • 并发场景:
    • 线程 A 和线程 B 同时触发扩容
    • 假设链表中有节点:a → b → null
    • 线程 A 执行到一半被挂起,此时已经将 a 迁移到新数组,形成 a → null
    • 线程 B 完整执行扩容,由于头插法,新链表变为 b → a → null
    • 线程 A 恢复执行,继续迁移 b,但此时 b.next 指向 a,而 a.next 在 B 线程中已经指向 null,但 A 线程仍按原链表迁移,可能导致 a.next = b,形成 b ⇄ a 的环形链表
  • JDK1.8,解决了该问题,但是多线程背景下,put 方法存在数据覆盖的问题。
  • JDK 1.8:并发插入 → 无锁判断 → 数据覆盖
    • 尾插法替代头插法:扩容时保持链表顺序,避免环形链表
    • 数据结构可升级为数组 + 红黑树(当链表长度 ≥ 8 且数组长度 ≥ 64)
    • 遗留问题:数据覆盖

在A线程通过Key计算出hashcode,通过hashcode计算出数组bucket中的下标,判断桶中没有头结点Node,接着由于网络波动原因,CPU时间片耗尽了。与此同时,线程B也通过key算出hashcode(可能通过hash冲突碰撞到了一起),然后也找到了bucket下标,判断桶中也没有头结点Node,然后插入Value,紧接着,线程A再次获得时间片,插入Value,最终线程B,数据被覆盖了,没有考虑到并发安全性。

JDK 版本核心问题原因
1.7死循环 + 数据丢失扩容时采用头插法,多线程并发迁移链表会导致环形链表
1.8数据覆盖无锁机制,多线程同时插入相同 key 或同一桶时,后插入的会覆盖先插入的
(7)解决多线程安全的方案
  • Collections.synchronizedMap(new HashMap<>()):对 HashMap 加全局锁,性能低;
  • Hashtable:底层对所有方法加synchronized,全局锁,性能差(已过时);
  • ConcurrentHashMap:高并发首选,采用分段锁(1.7)或 CAS+synchronized(1.8),锁粒度更细,性能高。

2. LinkedHashMap

  • 底层原理:哈希表(HashMap)+ 双向链表,既保留 HashMap 的高效查询,又保证元素有序(插入顺序或访问顺序);
  • 核心特性:
    • 有序性:双向链表维护元素顺序,默认按插入顺序遍历,可通过构造函数指定 “访问顺序”(accessOrder=true,最近访问的元素排在尾部);
    • 应用场景:LRU 缓存(基于访问顺序淘汰最久未使用的元素);
  • 与 HashMap 的关系:继承 HashMap,重写了 Node 节点(增加beforeafter指针),通过链表维护顺序。

3. ConcurrentHashMap(线程安全)

(1)JDK1.7 vs JDK1.8 实现差异
JDK 版本底层结构锁机制核心优势
1.7Segment 数组 + HashEntry 链表分段锁(每个 Segment 是独立锁)支持并发度(默认 16),不同 Segment 可并行操作
1.8数组 + 链表 + 红黑树CAS+synchronized(锁单个桶节点)锁粒度更细,性能更高;支持红黑树优化查询
(2)核心特点
  • 线程安全:通过锁机制保证高并发下的数据一致性;
  • 高效性:避免全局锁,支持多线程并行读写;
  • 功能完善:支持putIfAbsent()(不存在则插入)、size()(无锁统计)等并发安全方法。

四、Set 接口核心实现类

Set 接口的核心特点是无序、不可重复,其实现均依赖 Map 接口(用 Map 的 key 存储 Set 元素,value 为固定空对象)。

1. HashSet

  • 底层原理:基于 HashMap 实现,value 是一个固定的PRESENT对象(private static final Object PRESENT = new Object());
  • 去重机制:依赖 HashMap 的 key 唯一性,即插入元素时:
    1. 计算元素的 hash 值,定位到对应的桶;
    2. 通过hashCode()equals()方法判断是否存在相同元素;
    3. 存在则不插入,不存在则作为 key 存入 HashMap;
  • 核心特点:无序(插入顺序与遍历顺序不一致)、高效(查询、插入、删除均为 O (1) 时间复杂度)、非线程安全。

2. LinkedHashSet

  • 底层原理:基于 LinkedHashMap 实现,保留 LinkedHashMap 的有序性;
  • 核心特点:不可重复 + 有序(插入顺序),性能略低于 HashSet,但有序场景下更适用;
  • 与 HashSet 的区别:仅多了双向链表维护顺序,其他特性(去重机制、效率)与 HashSet 一致。

3. TreeSet

  • 底层原理:基于 TreeMap 实现,key 通过红黑树排序;
  • 核心特点:
    • 有序性:默认按元素的自然顺序(如 String 字典序、Integer 数值序)排序,也可通过Comparator自定义排序;
    • 去重机制:通过排序规则判断元素是否重复(而非hashCode()equals());
  • 适用场景:需要有序且去重的场景(如排序后的用户 ID 集合)。

五、核心面试高频考点总结

1. 基础类对比

  • ArrayList vs LinkedList:底层结构→性能差异→适用场景;
  • HashMap vs Hashtable:线程安全→底层结构→扩容机制(HashMap 初始容量 16,Hashtable 初始容量 11;HashMap 负载因子 0.75,Hashtable 0.75);
  • HashSet vs TreeSet:有序性→去重机制→性能。

2. 核心原理类问题

  • 为什么 ArrayList 扩容因子是 1.5?
  • 为什么 HashMap 的容量是 2 的次幂?
  • HashMap 的哈希扰动函数有什么作用?
  • HashMap 在 JDK1.8 为什么引入红黑树?
  • HashSet 如何保证元素不重复?

3. 多线程相关

  • HashMap 为什么是非线程安全的?JDK1.7 和 1.8 的问题差异?
  • 多线程下如何安全使用 HashMap?
  • ConcurrentHashMap 的锁机制演变?

4. 实践场景类

  • 什么场景用 ArrayList?什么场景用 LinkedList?
  • 如何实现一个 LRU 缓存?(LinkedHashMap 的访问顺序)
  • 高并发场景下,键值对存储首选什么集合?(ConcurrentHashMap)

六、复习总结

Java 集合的核心是 “底层结构决定性能,场景决定选型”,复习时需重点掌握:

  1. 每个核心集合的底层结构(数组、链表、哈希表、红黑树);
  2. 关键机制(扩容、树化、去重、锁机制)的原理;
  3. 不同场景下的集合选型(查询优先→ArrayList,增删优先→LinkedList,键值对→HashMap,高并发→ConcurrentHashMap);
  4. JDK 版本差异(尤其是 HashMap 和 ConcurrentHashMap)。

建议结合 “原理 + 面试题” 的方式复习,比如通过 “HashMap 的 put 流程” 串联哈希计算、冲突处理、扩容、树化等多个知识点,同时动手写简单示例(如 ArrayList 扩容测试HashMap 去重测试),加深理解。

http://www.dtcms.com/a/498667.html

相关文章:

  • 郑州建设厅官方网站地方网站推广
  • 宁波网站建设建站厂家wordpress 站点描述
  • 兴扬汽车网站谁做的公司设计图
  • 上海石化有做网站设计的吗广西网络广播电视台直播
  • 网站和推广在一家做的好处卓智网络科技有限公司
  • 推广网站有哪些做网站销售水果
  • 产品网站推广淄博做网站建设的公司
  • 整站下载器 安卓版企业网站多大空间够用
  • 博罗做网站战队头像在线制作免费
  • 东莞专业网站建设推广欧洲c2c平台
  • 手机网站做指向沃尔玛网上商城可以用购物卡吗
  • 中国建设银行北京市互联网网站成都小程序开发公司找哪家
  • 电商网站要素如何提升网站营销力
  • 成都模板网站建设服务深圳创纪录暴雨19小时
  • 网站地图后缀响应式博客网站模板
  • 嘉兴企业网站模板聚美优品
  • 展示网站模版源码wordpress 2栏主题
  • 网站建设与排名网站广告位代码
  • 营销型企业网站策划方案村网站开设两学一做栏目
  • 靓号网建站百度推广手机app下载
  • WordPress网站打不开nginx关wordpress更新
  • 四川省住房与城乡建设厅网站官网wordpress 中文版 英文版
  • aspx网站模板手机模块网站
  • 单位网站建设程序网站建设与推广的实训报告
  • 什么网站可以做投资做网站需要关注哪些
  • 做网站补贴网页广告拦截
  • 网站优化关键词怎么做贵阳搜索引擎排名推广
  • 网站设计制作托管维护网站备案公告
  • 网站以前在百度能搜索不到公司变更法人流程
  • 小语种网站建设 coverwordpress 页面 置顶