想必大家平时使用ArrayList<>的次数不少吧,那我们本次就从源码来看看ArrayList集合的庐山真面面吧
1. 成员变量介绍
首先,看下arraylist的成员变量
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 序列化版本号,用于序列化标记版本
private static final long serialVersionUID = 8683452581122892189L;
// 默认初始化容量:10,用于无参构造使用,后面再看
private static final int DEFAULT_CAPACITY = 10;
// 空数组EMPTY_ELEMENTDATA,带参构造使用
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认空数组 默认无参构造使用
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 定义一个object类型的数组变量,用于缓存数组元素,当第一个元素加入时,容量会拓增为10;
transient Object[] elementData;
// size变量,用于记录集合中的元素个数
private int size;
// 集合元素允许的最大数量,防止OutOfMemoryError错误!
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 小提问:
下记代码的输出结果是:
ArrayList<Object> list1 = new ArrayList<>(10);
System.out.println(list1.size());
2. 构造方法介绍
接下来我们一起看下构造方法
// 构造方法一
// 自定义初始化容量,当initialcapacity小于0时,则抛出IllegalArgumentException异常
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 如果initialcapacity > 0 新建一个object[]数组,并让elementData指向该数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 如果initialcapacity == 0,则让element指向EMPTY_ELEMENTDATA,提升效率
// 等效下记的无参构造方法!!!
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 抛出异常内容为:Illegal Capacity:录入的负数值
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
构造方法举例:
ArrayList<Object> list = new ArrayList<>(20);
// 无参构造方法,默认就是初始化容量为10的数据(这个是用的最多的一个构造方法)
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 参数为collection类型的带参构造方法
// 使用该方法需要留意的是以下2点:
1 集合C的参数类型需要是该被创建集合的元素的子类或与该被创建集合的元素类型一致.
2 传入的集合C 不能为null,否则会抛出NullPointerException空指针异常
public ArrayList(Collection<? extends E> c) {
// 调用toArray()方法,将集合转成数组(详见toArray()方法)
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 如果类型不一致,则进一步进行转换
if (elementData.getClass() != Object[].class)
// 调用Arrays.copyof()
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果集合元素为0,则让element指向EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
}
}
3. 自增原理
ok ,构造方法就是以上了,接下来就是相关的方法介绍了,getReady()?
ArrayList<>集合的方法很多,这里我们就跳几个常用的来看下,看方法之前我们先一起看下集合是如何进行自我扩容的
// 方法add()调用时会调用 ensureCapacityInternal()方法,该方法详细我们接着往下看
public boolean add(E e) {
// size + 1 的值会被传给ensureCapacityInternal()
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// 再传给calculateCapacity(elementData, minCapacity)
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 如果集合是调用的无参构造或类无参构造方法,则调用Math.max()取二者的最大值(确保最小值不小于10)
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 否则取传入的值
return minCapacity;
}
// 之后在调用ensureExplicitCapacity(int value)方法
private void ensureExplicitCapacity(int minCapacity) {
// 继承自abstractList的一个常量,这里不做说明.
modCount++;
if (minCapacity - elementData.length > 0)
// 调用grow(int value)方法
grow(minCapacity);
}
private void grow(int minCapacity) {
// 首先获取当前的集合元素个数oldCapacity
int oldCapacity = elementData.length;
// oldCapacity右移1位,约等于1/2(apacity)
// 最终得到的newCapacity的值约原有集合元素个数的1.5倍.
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0){
// 如果newCapacity小于inCapacity
// 个例子,原有集合元素个数为1,此时右移一位,值是?,度娘会告诉你的)
newCapacity = minCapacity;
}
// 极端条件下,集合非常大时
if (newCapacity - MAX_ARRAY_SIZE > 0){
// 否则调用hugeCapacity()方法
newCapacity = hugeCapacity(minCapacity);
}
// 一系列的方法调用的结果就是限制了minCapacity的取值,使其尽可能得接近集合元素的个数
// 引用源码的原话就是:minCapacity is usually close to size, so this is a win!
elementData = Arrays.copyOf(elementData, newCapacity);
}
//
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE : // Integer类的可表示的最大值 MAX_VALUE = 0x7fffffff;
MAX_ARRAY_SIZE;
}
总结:
集合的扩增本质就是通过一系列方法限定newCapacity的值,然后在调用Arrays.copyOf(elementData, newCapacity)复制原有的数组元素.生成新数组.
4. 常用方法介绍
OK 自增看完,还是佩服大佬们的idea,加油吧!接下来就是比较愉快的方法介绍了
public boolean add(E e) {
ensureCapacityInternal(size + 1);
// 数组的size+1位置赋值为e
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
// 判断indexv值的合理性,往下看
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
// 之后在赋值原理的数组,,把原index位置的元素往右移动一位,空出来index位置
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 再把新数组index位置的值赋值为element
elementData[index] = element;
size++;
}
// System类的arraycopy(object...)方法
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
src 被复制的数组(目标数组).
srcPos 从目标数组pos位置开始复制
dest 新数组(被复制的元素放这里).
destPos 被复制的元素从新数组的destpos位置开始放置
length 被复制元素的个数
private void rangeCheckForAdd(int index) {
// 如果index不合理,,则抛出IndexOutOfBoundsException索引越界异常!熟悉吧^_^
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
public E remove(int index) {
rangeCheck(index);
modCount++;
// 获得index位置的元素
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
// 如果remove的不是最后一个元素,则index位置右边的元素全部左移一位.
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 之后再将最后位置的值赋值为null,让GC能开启清理工作
elementData[--size] = null;
// 返回该位置的元素
return oldValue;
}
private void rangeCheck(int index) {
// 如果index不合理,,则抛出IndexOutOfBoundsException索引越界异常!同Add()方法的检查
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
public boolean remove(Object o) {
if (o == null) {
// 传入的元素如果为null,遍历数组,移除第一个null元素
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 传入的元素如果为不为null,遍历数组,如果有o存在,则移除第一个o元素
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
// 不存在元素o时,返回false;
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
// 同remove工作原理.
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
// 这个get()就不用解释了吧各位..
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
5. 总结
源码千千万,理解一个是一个,阅读源码的目的不在于阅读,而在于学习理解大佬们的思考方法,
初次投稿,如有失误,还请指点
代码千千万,我们下一篇见.