java中的数据结构之集合框架源码分析的系列文章如下:
JAVA集合框架源码分析1-ArrayList
JAVA集合框架源码分析2-Stack
JAVA集合框架源码分析3-LinkedList
数据结构中有多种存储结构,如表和树,表中典型的是线性表,这种数据结构在java中最常见的就是ArrayList(顺序表)和LinkedList(链表),本文将着重对ArrayList的源码进行分析
1.继承关系
查看源码可知ArrayList是AbstractList的子类,而AbstractList是AbstractCollection的抽象子类,AbstractCollection实现了Collection接口,同时List是Collection接口的子接口,而且ArrayList实现了List接口,现在主要查看下List类中的源码,既然集合是存储数据的,那么必定存在增删改查四个操作,可以查看到List接口中有对应的如下四个方法
boolean add(E e);
boolean remove(Object o);
E set(int index, E element);
E get(int index);
2.ArrayList的成员变量
private static final long serialVersionUID = 8683452581122892189L;
// 默认的空间大小
private static final int DEFAULT_CAPACITY = 10;
//定义空数组,在有参数的构造方法(参数为容器大小),若参数为0,则给存储元素的数组赋值为空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//定义空数组,在无参数的构造函数中使用,给存储元素的数组赋值为空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//定义数组用于存储容器中的元素
transient Object[] elementData; // non-private to simplify nested class access
//集合中元素的个数
private int size;
3.ArrayList的构造方法
//构造方法的中的参数为容器的大小,逻辑关系很简单,
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() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//使用一个集合作为构造方法的参数
public ArrayList(Collection extends E> c) {
//将参数转为数组并赋值给当前的数组容器
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray可能返回的不是 Object[] ,所以做一层类型的判断将其转为Object数组
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//若数组容器的长度为0,则将数组容器赋值为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
4.操作——增加
第一种添加方法
注意以下方法的调用关系
public boolean add(E e) {
//确保数组容器有足够的存储空间,size为数组容器的元素个数
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//说明是添加第一个元素,此时的容器大小为0,那么会在首次将容器的大小设定为默认大小,即10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 确认是否需要扩容,如果元素个数+1即(size+1)>数组的长度,表示已经存满,需要进行扩容
if (minCapacity - elementData.length > 0)
//扩容
grow(minCapacity);
}
private void grow(int minCapacity) {
// 旧数组容器的大小
int oldCapacity = elementData.length;
//新数组容器的大小为旧的大小+旧的大小的一半,即ArrayList默认的扩容了当前数组大小的一半。
int newCapacity = oldCapacity + (oldCapacity >> 1);
//确保了newCapacity不会比minCapacity小
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//确保了newCapacity的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//将数据拷贝到新数组容器中,并让elementData引用。
elementData = Arrays.copyOf(elementData, newCapacity);
}
第二种添加方法
public void add(int index, E element) {
//确认添加元素的添加范围是否正确
rangeCheckForAdd(index);
//同上,确认是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//从需要插入元素位置开始的所有元素向后"移动"1位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将在插入的位置赋值为插入的元素
elementData[index] = element;
//将数组长度增加1
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
第三种增加方法
public boolean addAll(Collection extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
////同上,确认是否需要扩容
ensureCapacityInternal(size + numNew); // Increments modCount
//将需要添加的元素数组添加到数组容器的尾部。
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
第四种添加方法
public boolean addAll(int index, Collection extends E> c) {
//确认添加范围是否符合要求
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
//同上,确认是否需要扩容
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
// 从需要插入元素位置开始的所有元素向后"移动"numNew 位
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//将a数组拷贝到新数组。从index位置开始到index+numNew
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
5.操作——删除
第一种方法:根据角标删除
public E remove(int index) {
//删除的角标的范围检查
rangeCheck(index);
modCount++;
//获取到要删除的元素
E oldValue = elementData(index);
//删除元素时数组需要移动的元素个数
int numMoved = size - index - 1;
//从index+1位置开始的元素统一向左移动一位,导致index位置的元素会被后面的元素覆盖,从而起到删除的作用
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将原数组的最后一个元素设置为null
elementData[--size] = null; // clear to let GC do its work
//返回删除的元素
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
第二种方法:直接删除元素
public boolean remove(Object o) {
//间接证明可以添加null元素
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++)
//使用equal对元素进行对比,找到元素
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//删除方面和上面的删除方法一样
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
第三种删除方法:清空所有
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
第四章删除方法
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
//将数组中从toIndex位置开始的元素移动(toIndex-fromIndex)位
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
//将newSize位置开始的元素设定为null,便于回收。
elementData[i] = null;
}
size = newSize;
}
5.操作——修改
public E set(int index, E element) {
// 确认修改范围合法
rangeCheck(index);
//获取原来的元素
E oldValue = elementData(index);
//设置新的元素
elementData[index] = element;
//返回原来的元素
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
6.操作——查找
//不做解释
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
7.总结
1.ArrayList底层实现是数组。
2.当使用无参数构造函数创建ArrayList对象时,ArrayList对象中的数组初始长度为0,在第一次添加时才会设置数组的长度为默认长度即10
3.ArrayList扩容是将元素重新拷贝到新数组中,扩容的大小为旧数组的长度加上旧数组长度的一半。
4.ArrayList数据结构的特性导致查改操作比较快,而增删操作比较慢。