在java中我们或多或少都使用过ArrayList集合,但是它背后的工作原理到底是怎样的呢?今天我和大家一起进入ArrayList的家(源码)来学习。
在进入ArrayList源码之前,我们需要了解什么是顺序存储,因为ArrayList的进行存储时使用的就是顺序存储,大家可以去我的博客数据结构—顺序存储了解下。
使用ArrayList的步骤:
1、需要使用ArrayList<E> list = new ArrayList<>();
初始化ArrayList,得到其对象
2、使用得到的该对象进行一波猛如虎的增删改查等操作
(一)获取ArrayList对象
1、整数构造
transient Object[] elementData;
private static final Object[] 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);
}
}
从源码中可以看到,使用整数构造获取对象时,首先是对参数判断,如果传入的整数参数大于0,那么创建一个新的Object数组,其中this.elementData是对应的源码是transient Object[] elementData;
transient的作用是标注其变量不参与序列化过程。参数等于0时elementData等于空数组,其他情况的话抛出异常
2、无参构造
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3、集合构造
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
c.toArray()的作用是将集合转化成数组,elementData.getClass() != Object[].class
的作用是判断c.toArray()转化的数组是否为Object类型(因为传入自定义的ArrayList可能c.toArray()返回的不是Object类型),如果不是则使用Arrays.copyOf(elementData, size, Object[].class);
重新拷贝,不过它的最终目的都是为了返回Object类型的数组。其方法对应的源码是
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
> /**三目运算,newType是否是Object类型,如果是copy =
new Object[newLength],否则copy=Array.newInstance(newType.getComponentType(), newLength)**/
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
public static Object newInstance(Class<?> componentType, int length)
throws NegativeArraySizeException {
return newArray(componentType, length);
}
private static native Object newArray(Class<?> componentType, int length)
throws NegativeArraySizeException;
其中native关键字修饰的方法说明该方法是原生态方法,它的实现不是在当前文件中,而是在用其它语言(如c或c++)实现的文件。这样做的原因是因为java语言本身不能对操作系统底层进行操作和访问的,它必须通过JNI接口调用其它语言来实现对操作系统底层的访问。
(二)添加数据
1、add(E e)方法
private int size;
private static final int DEFAULT_CAPACITY = 10;
//将指定的元素追加到列表的末尾
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//对elementData数组初始化
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果使用的是无参构造,返回的初始值为10
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);
}
//扩容
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// >>右移符号,>>1相当于除于2的1次方
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果添加的数组下标比它原来数组长度的1.5倍还要大
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果扩容后的大小大于MAX_ARRAY_SIZE的值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 将elementData复制到扩容后的数组中,并elementData引用扩容后的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
//数组的最大容量
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
add(E e)方法中使用calculateCapacity(Object[] elementData, int minCapacity)
来初始化数组elementData容量,使用ensureExplicitCapacity(int minCapacity)
对数组中的容量进行判断,判断是否需要扩容; grow(minCapacity)
是扩容的具体实现方法,oldCapacity + (oldCapacity >> 1)
表示将数组的长度增加为原来的1.5倍,如果添加的数组比它容量的1.5倍大但是比Integer.MAX_VALUE-8小,那么它的容量就是它本身
2、add(int index, E element)方法
//将指定的元素插入其中的指定位置
public void add(int index, E element) {
//elementData数组下标的判断
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//留index位置出来
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//判断index是否符合条件
private void rangeCheckForAdd(int index) {
//如果插入的位置小于o或者大于数组elementData的长度则抛出异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
rangeCheckForAdd(index)
方法判断插入的位置是否符合条件,ensureCapacityInternal(size + 1)
方法进行数组的初始化和判断是否需要扩容,具体的分析请看上面add(E e)
方法的的分析,
System.arraycopy(elementData, index, elementData, index + 1,size - index);
的作用是将数组elementData中index位置后的数据后移一位(将index位置空出来)
3、addAll(Collection<? extends E> c)方法
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray(); //将集合c转化成数组并将其赋给数组a
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
(三)删除数组
1、remove(int index)方法
public E remove(int index) {
//判断index是否符合条件
rangeCheck(index);
modCount++;
//oldValue等于elementData数组中index的数据
E oldValue = elementData(index);
//得到index之后的个数
int numMoved = size - index - 1;
if (numMoved > 0)
//将index后面的数组前移一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
//判断index是否符合条件
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//返回elmentData中index的数据
E elementData(int index) {
return (E) elementData[index];
}
2、remove(Object o)方法
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
//得到index数据之后的个数
int numMoved = size - index - 1;
if (numMoved > 0)
//将index之后的数据前移一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
3、clear() 方法
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
ArrayList删除数据有3个方法,它们分别是根据下标index删除、根据数据Object删除和全部删除,前两个方法删除数据的原理都是一样的,都是将从index后面的数据前移一位,只不过第二个方法(remove(Object o))比第一个方法(remove(int index))多了查询index的过程而已,第三个方法删除数据的原理很简单,将elementData中的全部数据修改为null,并且长度为0
(四)修改数据
public E set(int index, E element) {
rangeCheck(index);
//获取elementData数组中index位置的数据
E oldValue = elementData(index);
//将elementData数组中index位置的数据修改为element
elementData[index] = element;
return oldValue;
}
(五)查询数据
public E get(int index) {
rangeCheck(index);
//返回下标为index的数据
return elementData(index);
}
以上是我对ArrayList中增、删、改、查的分析,如有不足请大家指出,谢谢(*^_^*)