本文共 3705 字,大约阅读时间需要 12 分钟。
阅读源码是一项非常有价值的技能,它不仅能帮助我们更好地理解代码的运行机制,还能为我们的编程能力提供宝贵的提升。今天,我们以ArrayList为例,来探讨一下它的源码结构以及背后的逻辑。
ArrayList是一个非常常用的Java集合,它的类签名为:
public class ArrayListextends AbstractList implements List , RandomAccess, Cloneable, java.io.Serializable
从类签名可以看出,ArrayList继承自AbstractList,并实现了List、RandomAccess、Cloneable以及序列化接口。这些接口赋予了ArrayList丰富的功能,例如支持随机访问、克隆操作以及序列化。
ArrayList内部维护了一些关键变量来管理数据:
// 序列化版本号private static final long serialVersionUID = 8683452581122892189L;// 常量,默认容量为10private static final int DEFAULT_CAPACITY = 10;// 常量,初始化一个空的Object数组private static final Object[] EMPTY_ELEMENTDATA = {};// 常量,初始化一个空的Object数组,与EMPTY_ELEMENTDATA用于区别初始化时指定容量0还是默认不指定private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};// 变量,真正用来存储元素的数组名transient Object[] elementData;// 数组中实际存储的元素数量,未初始化则默认为0private int size; 这些变量中,EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别在于它们用于不同的初始化场景。例如,当使用new ArrayList(0)时,会使用EMPTY_ELEMENTDATA;而使用new ArrayList()或new ArrayList(10)时,则会使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
ArrayList提供了多个构造方法来满足不同场景的需求:
// 不指定初始容量的构造函数public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}// 指定初始容量的构造函数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); }}// 通过已有集合直接构造public ArrayList(Collection c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; }} 这些构造方法的主要区别在于它们如何初始化elementData数组。通过选择不同的构造方法,可以优化ArrayList的初始化性能和内存使用。
ArrayList的常用方法包括add、remove、contains等。以add方法为例:
public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true;} 在添加元素之前,add方法会检查当前数组的容量是否足够。如果不够,则会调用ensureCapacityInternal方法进行扩容。
private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity;}private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) { grow(minCapacity); }} ensureCapacityInternal方法首先通过calculateCapacity计算所需的最小容量,然后调用ensureExplicitCapacity进行扩容操作。扩容时会调用grow方法来增加数组的大小。
private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity > 1 ? oldCapacity / 2 : oldCapacity); if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } if (newCapacity - MAX_ARRAY_SIZE > 0) { newCapacity = hugeCapacity(minCapacity); } elementData = Arrays.copyOf(elementData, newCapacity);} grow方法根据当前数组的大小和所需容量,决定如何扩展数组。它会尽量增加数组大小,但不会超过MAX_ARRAY_SIZE或Integer.MAX_VALUE。
在创建ArrayList时:
不指定初始容量:
List list1 = new ArrayList();
这将使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA来初始化数组。第一次添加元素时,数组会扩容到默认容量10。
指定初始容量为0:
List list2 = new ArrayList(0);
这将使用EMPTY_ELEMENTDATA来初始化数组。第一次添加元素时,数组会扩容到1。
指定初始容量为10:
List list4 = new ArrayList(10);
这将直接创建一个容量为10的数组,无需扩容。
通过选择合适的构造方法,可以避免不必要的扩容操作,从而提高性能。
通过对ArrayList源码的分析,我们可以更深入地理解其内部工作机制。阅读源码不仅能帮助我们掌握专业技能,还能让我们意识到代码背后的设计理念和优化思路。建议在阅读源码时,从类签名开始,逐步深入了解变量、构造方法和常用方法。同时,注意观察代码中的设计模式和算法,这将有助于我们在实际编程中做出更好的决策。
转载地址:http://efpbz.baihongyu.com/