【Java集合夜话】第2篇:Collection家族,一场优雅的探索之约

在上一章中,我们对Java集合框架有了整体认识。🌹
现在,让我们深入探索Collection家族中的每位"明星成员",不仅要了解它们的特性,更要掌握一些实用的记忆技巧,帮助你对这些集合类型烂熟于心。🌹
如有描述不准确之处,欢迎大家指出交流。🌹


深度求索

Java Collection接口家族详解

在Java集合框架中,Collection接口家族就像一个精心设计的工具箱,每个工具都有其独特的用途。本文将深入介绍这些"明星"集合类,带你掌握它们的特点和最佳实践。

Collection框架概览

下面用一张图来表示Collection家族之间的关系,以及里面的一些常见集合的优缺点。

Collection框架体系

Collection接口家族主要成员

  • List: 有序可重复集合
    • ArrayList:最常用的List实现,
    • LinkedList:适合频繁增删的场景
    • Vector:早期的线程安全实现
  • Set: 不重复集合
    • HashSet:无序唯一集合
    • LinkedHashSet:有序唯一集合
    • TreeSet:排序唯一集合
  • Queue: 队列集合
    • PriorityQueue:优先队列
    • Deque:双端队列
    • Stack:后进先出栈
    • ArrayDeque:数组双端队列
    • ConcurrentLinkedQueue:并发队列

List详解

由浅

常见实现类对比

ArrayList
  • 基于动态数组实现
  • 查询速度快,插入和删除慢
  • 需要考虑扩容机制
  • 最常用的List实现

最常用的List实现,是基于动态数组实现(之所以叫动态是因为数组容量可以动态变化)、查询速度快、插入和删除慢。这点很容易理解,数组一般需要一开始就定义好容量,如果空间不够用了, 需要扩容,重新定义一个大的空间(怎么定义大空间又涉及到扩容机制,这里浅的时候咱不细谈)。然后将元素全部在放到这个空间中去,这里的操作会有时间成本问题。但是因为是数组实现,可以通过寻址立马查到某一个元素,所以效率高。

LinkedList
  • 基于双向链表实现
  • 插入删除快,查询慢
  • 不存在扩容问题
  • 适合频繁增删场景

适合频繁增删的场景,LinkedList可以和ArrayList做一个鲜明的对比:LinkedList采用双向链表实现,由一个个Node节点通过前后指针相连。由于链表结构的特性,它不需要像数组那样预分配连续空间,只要堆内存充足就可以不断添加节点。在插入操作时,只需修改插入位置前后节点的指针指向即可,不需要像ArrayList那样移动大量元素;删除操作也只需要修改待删除节点前后节点的指针指向,操作复杂度都是O(1)。但需要注意的是,虽然增删效率高,但查找元素时需要遍历链表,性能不如ArrayList。

Vector
  • 线程安全版ArrayList
  • 性能较差(synchronized实现)
  • 已被CopyOnWriteArrayList替代

早期的线程安全实现, Vector又可以和ArrayList做一个鲜明的对比:两个都是基于动态数组实现,但是Vector是线程安全的,所以他的方法都加了synchronized关键字,但正是由于这个原因,所以它的效率低。
这里大家要知道synchronized是一个重量级的锁,在并发量大的情况下,性能会下降。所以我们基本上很少使用Vector。而是使用CopyOnWriteArrayList。

源码深入分析

入深

1. ArrayList源码解析
// 底层数组实现
transient Object[] elementData;

// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;

// 核心扩容方法
private void grow(int minCapacity) {
   
   
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为原来的1.5倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

ArrayList的核心原理主要体现在以下几个方面:

  1. 扩容机制
  • 当容量不足时,会触发grow方法进行扩容
  • 默认扩容为原容量的1.5倍(oldCapacity + oldCapacity >> 1)
  • 扩容过程中会创建新数组并复制元素,性能开销较大
  • 因此在已知元素数量的情况下,建议使用带初始容量的构造方法
  1. 线程安全性
  • ArrayList是非线程安全的
  • 多线程并发修改时可能导致数据不一致
  • 如需线程安全可以:
    1. 使用Collections.synchronizedList()包装
    2. 使用CopyOnWriteArrayList
    3. 自行加锁控制并发访问
  1. 删除元素原理
  • 删除指定位置元素时,会将该位置后的所有元素向前移动一位
  • 元素移动通过System.arraycopy实现
  • 删除操作的时间复杂度为O(n)
  • 频繁删除场景建议使用LinkedList
  1. 遍历性能
  • 支持随机访问,通过索引访问元素的时间复杂度为O(1)
  • for循环遍历性能优于迭代器遍历
  • 不要在遍历过程中对List进行结构性修改,会导致ConcurrentModificationException

这些特性决定了ArrayList适合读多写少的场景,不适合频繁增删和并发访问的场景。在使用时需要根据实际情况权衡选择合适的List实现类。

2. LinkedList源码解析
// 节点定义
private static class Node<E> {
   
   
    E item;
    Node<E> next;
    Node<E> prev;
    
    Node(Node<E> prev, E element, Node<E> next) {
   
   
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

// 头尾指针
transient Node<E> first;
transient Node<E> last;

// 添加元素实现
void linkLast(E e) {
   
   
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null</
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值