深入解析ArrayList源码:从短链项目实战到底层原理

深入解析ArrayList源码:从短链项目实战到底层原理

前言

在Java开发中,ArrayList是我们最常用的集合类之一。本文将结合一个实际的短链项目,深入分析ArrayList的源码实现,帮助大家理解其底层原理和设计思想。

1. 项目背景:短链系统中的ArrayList应用

在我们的短链项目中,ArrayList被广泛使用。让我们先看几个典型的应用场景:

1.1 统计数据收集

java 复制代码
// 短链接访问统计数据收集
List<ShortLinkStatsAccessDailyRespDTO> daily = new ArrayList<>();
List<ShortLinkStatsLocaleCNRespDTO> localeCnStats = new ArrayList<>();
List<Integer> hourStats = new ArrayList<>();

1.2 批量数据处理

java 复制代码
// 批量创建短链接
List<ShortLinkBatchCreateRespDTO> result = new ArrayList<>();
for (int i = 0; i < requestParam.getOriginUrls().size(); i++) {
    // 处理每个URL并添加到结果列表
    result.add(createShortLink(createReqDTO));
}

1.3 查询结果存储

java 复制代码
// 存储分组查询结果
List<GroupDO> groupDOList = baseMapper.selectList(queryWrapper);
if (CollUtil.isNotEmpty(groupDOList) && groupDOList.size() == groupMaxNum) {
    throw new ClientException("已超出最大分组数");
}

这些场景都体现了ArrayList的核心特点:动态扩容随机访问顺序存储

2. ArrayList核心源码解析

2.1 类定义和继承关系

java 复制代码
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    // 默认初始容量
    private static final int DEFAULT_CAPACITY = 10;
    
    // 空数组实例
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    // 默认大小的空数组实例
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    // 存储ArrayList元素的数组缓冲区
    transient Object[] elementData;
    
    // ArrayList的大小(包含的元素数量)
    private int size;
}

关键点解析:

  • RandomAccess:标记接口,表示支持快速随机访问
  • transient Object[] elementData:实际存储数据的数组,transient表示不参与序列化
  • size:当前元素个数,注意区别于数组容量

2.2 构造方法深度解析

2.2.1 无参构造方法
java 复制代码
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

在短链项目中的应用:

java 复制代码
// 创建空的统计数据列表,后续动态添加
List<ShortLinkStatsAccessDailyRespDTO> daily = new ArrayList<>();

设计思想:

  • 延迟初始化:不立即分配内存,节省资源
  • 默认容量:首次添加元素时才分配默认容量10
2.2.2 指定容量构造方法
java 复制代码
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
}

在短链项目中的优化应用:

java 复制代码
// 如果知道大概的数据量,可以预设容量避免扩容
List<ShortLinkBatchCreateRespDTO> result = new ArrayList<>(requestParam.getOriginUrls().size());

2.3 核心方法源码解析

2.3.1 add方法 - 动态扩容的核心
java 复制代码
public boolean add(E e) {
    modCount++;  // 修改次数+1,用于fail-fast机制
    add(e, elementData, size);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();  // 扩容
    elementData[s] = e;
    size = s + 1;
}

扩容机制详解:

java 复制代码
private Object[] grow() {
    return grow(size + 1);
}

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = ArraysSupport.newLength(oldCapacity,
            minCapacity - oldCapacity, /* minimum growth */
            oldCapacity >> 1           /* preferred growth */);
    return elementData = Arrays.copyOf(elementData, newCapacity);
}

扩容策略分析:

  1. 扩容时机:当前大小等于数组长度时
  2. 扩容大小:新容量 = 旧容量 + 旧容量/2(即1.5倍)
  3. 数据迁移 :使用Arrays.copyOf复制到新数组

在短链项目中的性能影响:

java 复制代码
// 不好的做法:频繁扩容
List<String> urls = new ArrayList<>();  // 默认容量10
for (int i = 0; i < 1000; i++) {
    urls.add("url" + i);  // 会触发多次扩容
}

// 好的做法:预设容量
List<String> urls = new ArrayList<>(1000);  // 避免扩容
for (int i = 0; i < 1000; i++) {
    urls.add("url" + i);
}
2.3.2 get方法 - 随机访问的实现
java 复制代码
public E get(int index) {
    Objects.checkIndex(index, size);  // 边界检查
    return elementData(index);
}

E elementData(int index) {
    return (E) elementData[index];  // 直接数组访问
}

性能分析:

  • 时间复杂度:O(1)
  • 实现原理:直接通过数组下标访问

在短链项目中的应用:

java 复制代码
// 高效的随机访问
List<ShortLinkStatsAccessDailyRespDTO> dailyStats = getDailyStats();
for (int i = 0; i < dailyStats.size(); i++) {
    ShortLinkStatsAccessDailyRespDTO stat = dailyStats.get(i);  // O(1)时间复杂度
    // 处理统计数据
}
2.3.3 remove方法 - 元素删除的实现
java 复制代码
public E remove(int index) {
    Objects.checkIndex(index, size);
    final Object[] es = elementData;
    
    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    fastRemove(es, index);
    
    return oldValue;
}

private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);  // 数组元素前移
    es[size = newSize] = null;  // 清除引用,帮助GC
}

删除机制分析:

  1. 元素移动:删除位置后的所有元素前移一位
  2. 时间复杂度:O(n),最坏情况需要移动n-1个元素
  3. 内存管理:将最后一个位置置null,帮助垃圾回收

3. ArrayList vs LinkedList:在短链项目中的选择

3.1 性能对比

操作 ArrayList LinkedList 短链项目场景
随机访问 O(1) O(n) 统计数据查询 ✓
头部插入 O(n) O(1) 很少使用
尾部插入 O(1)* O(1) 批量数据收集 ✓
中间插入 O(n) O(1)** 很少使用
删除 O(n) O(1)** 偶尔使用

摊销时间复杂度,可能触发扩容
需要先定位到节点

3.2 内存使用对比

java 复制代码
// ArrayList内存布局(更紧凑)
[data1][data2][data3][null][null]...

// LinkedList内存布局(额外指针开销)
[prev|data1|next] -> [prev|data2|next] -> [prev|data3|next]

在短链项目中的选择:

java 复制代码
// 适合ArrayList的场景:统计数据展示
List<ShortLinkStatsAccessDailyRespDTO> dailyStats = new ArrayList<>();
// 频繁的随机访问,内存使用效率高

// 不适合LinkedList的场景:
// 1. 需要根据索引快速访问统计数据
// 2. 数据量大时,LinkedList的指针开销明显

4. ArrayList的线程安全问题

4.1 并发修改异常

java 复制代码
// 在短链项目中可能遇到的问题
List<String> urls = new ArrayList<>();
urls.add("url1");
urls.add("url2");

// 遍历时修改会抛出ConcurrentModificationException
for (String url : urls) {
    if (url.contains("invalid")) {
        urls.remove(url);  // 危险操作!
    }
}

解决方案:

java 复制代码
// 方案1:使用Iterator的remove方法
Iterator<String> iterator = urls.iterator();
while (iterator.hasNext()) {
    String url = iterator.next();
    if (url.contains("invalid")) {
        iterator.remove();  // 安全删除
    }
}

// 方案2:使用removeIf方法(推荐)
urls.removeIf(url -> url.contains("invalid"));

// 方案3:在短链项目中,使用线程安全的集合
List<String> safeUrls = Collections.synchronizedList(new ArrayList<>());

4.2 多线程环境下的选择

java 复制代码
// 在短链项目的统计模块中
public class ShortLinkStatsService {
    
    // 线程不安全的做法
    private List<StatsRecord> records = new ArrayList<>();
    
    // 线程安全的做法
    private List<StatsRecord> safeRecords = new CopyOnWriteArrayList<>();
    
    // 或者使用同步
    private List<StatsRecord> syncRecords = Collections.synchronizedList(new ArrayList<>());
}

5. ArrayList性能优化实践

5.1 容量预设优化

java 复制代码
// 在短链项目中的优化实践
public List<ShortLinkBatchCreateRespDTO> batchCreateShortLinks(
        ShortLinkBatchCreateReqDTO requestParam) {
    
    List<String> originUrls = requestParam.getOriginUrls();
    // 预设容量,避免扩容开销
    List<ShortLinkBatchCreateRespDTO> result = new ArrayList<>(originUrls.size());
    
    for (int i = 0; i < originUrls.size(); i++) {
        // 批量处理逻辑
        result.add(createSingleShortLink(originUrls.get(i)));
    }
    
    return result;
}

5.2 内存优化

java 复制代码
// 及时释放不需要的引用
public void processLargeDataSet() {
    List<LargeObject> largeList = new ArrayList<>(10000);
    
    // 处理数据...
    
    // 处理完成后,及时清理
    largeList.clear();  // 清除所有元素
    largeList = null;   // 释放引用
}

5.3 批量操作优化

java 复制代码
// 使用addAll进行批量添加
List<String> allUrls = new ArrayList<>();
List<String> batchUrls = getBatchUrls();

// 好的做法:批量添加
allUrls.addAll(batchUrls);

// 不好的做法:逐个添加
for (String url : batchUrls) {
    allUrls.add(url);  // 可能触发多次扩容
}

6. 实际项目中的最佳实践

6.1 合理选择初始容量

java 复制代码
// 根据业务场景选择合适的初始容量
public class ShortLinkStatsCollector {
    
    public List<HourlyStats> collectHourlyStats() {
        // 一天24小时,预设容量24
        List<HourlyStats> hourlyStats = new ArrayList<>(24);
        
        for (int hour = 0; hour < 24; hour++) {
            hourlyStats.add(getStatsForHour(hour));
        }
        
        return hourlyStats;
    }
    
    public List<DailyStats> collectMonthlyStats() {
        // 一个月最多31天,预设容量31
        List<DailyStats> monthlyStats = new ArrayList<>(31);
        // ...
        return monthlyStats;
    }
}

6.2 避免不必要的装箱拆箱

java 复制代码
// 对于基本类型,考虑使用专门的集合
// 不好的做法:
List<Integer> ids = new ArrayList<>();  // 会有装箱开销

// 更好的做法:使用第三方库如Trove或Eclipse Collections
// TIntArrayList ids = new TIntArrayList();  // 避免装箱

6.3 合理使用Stream API

java 复制代码
// 在短链项目中处理统计数据
public List<String> getActiveShortLinks(List<ShortLinkDO> allLinks) {
    return allLinks.stream()
            .filter(link -> link.getEnableStatus() == 0)  // 过滤启用的链接
            .filter(link -> link.getDelFlag() == 0)       // 过滤未删除的链接
            .map(ShortLinkDO::getFullShortUrl)            // 提取URL
            .collect(Collectors.toList());                // 收集到新的ArrayList
}

7. 总结

通过对短链项目中ArrayList使用场景的分析,我们深入了解了ArrayList的:

  1. 底层实现:基于动态数组,支持自动扩容
  2. 性能特点:随机访问O(1),插入删除O(n)
  3. 扩容机制:1.5倍扩容,使用Arrays.copyOf迁移数据
  4. 线程安全:非线程安全,需要额外同步措施
  5. 优化策略:预设容量、批量操作、及时释放引用

在实际项目中,选择合适的集合类型需要根据具体的使用场景:

  • 频繁随机访问:选择ArrayList
  • 频繁插入删除:考虑LinkedList
  • 线程安全需求:使用CopyOnWriteArrayList或同步包装
  • 大量数据处理:注意内存使用和GC影响

希望这篇文章能帮助大家更好地理解和使用ArrayList,在实际项目中做出更好的技术选择!


参考资料:

  • OpenJDK ArrayList源码
  • 《Java并发编程实战》
  • 《Effective Java》第三版