开发十年,就只剩下这套架构体系了! >>>
java集合框架中被使用最多的可能就是ArrayList了,用它代替数组确实给我们开发带来了很大的便利。很多人都知道它是自动扩容的数组,使用的时候不用考虑数组的容量大小,可以往里面添加无数的元素,这样的理解是否正确,我们通过源码来分析。
java集合框架包括了很多接口和类,为了最大程度地提高代码的复用性和易用性,作者是运用了一些软件设计原则或者设计模式,我们可以从下面ArrayList的类图中看到其中的妙处。
从上图中,可以看到ArrayList是接口Collection和接口List的实现类,这里运用了对接口编程的设计原则(也可以说是依赖倒置)。这里也可以符合开闭原则,就是假如要添加一个新的集合类,可以让它直接实现Collection接口。这里还考虑了代码复用的原则,AbstractCollection,AbstractList封装了部分共用的代码,子类只要继承这些抽象类就可以获得,简化了编程。
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
{
// 序列化id
private static final long serialVersionUID = 8683452581122892189L;
// 默认数组初始容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组对象
private static final Object[] EMPTY_ELEMENTDATA = {};
// 数组对象,就是存储ArrayList元素的数组
transient Object[] elementData;
// ArrayList元素个数
private int size;
}
从以上代码和类图中可以看到,ArrayList继承自AbstractList,实现了List接口。其中父类AbstractList中也是实现了List的,为什么还要在ArrayList中实现List接口呢?这个估计是作者为了让ArrayList支持动态代理,具体可以点击这里查看原因分析。
从代码中也能看到,ArrayList其实就是Object[]数组,对ArrayList的操作其实就是对数组的操作,其数据结构如下所示:
// 无参构造器
public ArrayList() {
super();
// 把数组赋值为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
// 把其他集合的元素加到ArrayList
public ArrayList(Collection extends E> c) {
// 把c返回的数组直接加到ArrayList数组中
elementData = c.toArray();
size = elementData.length;
// 如果c.toArray不是返回Object[],再做进一步处理,转为Object[]
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
// 指定数组的初始容量
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
// 根据指定的大小创建数组
this.elementData = new Object[initialCapacity];
}
1.add(E e)方法。往数组后面插入元素,保持先进先出的特性。
// 把新元素加到ArrayList
public boolean add(E e) {
// 检查是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 新元素加入数组
elementData[size++] = e;
return true;
}
// 保证数组容量
private void ensureCapacityInternal(int minCapacity) {
// 如果数组为空数组,则数组容量取默认容量和需要插入位置的最大值
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 确保数组容量
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
// fast-fail机制
modCount++;
// 如果插入位置超过数组容量就扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 扩容方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 扩容新容量是旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果1.5倍不够大,就使用指定的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量超过了设定的最大数组大小,则新容量使用int的最大值,hugeCapacity方法返回的最大值为int的最大值,
// 因此ArrayList的最大容量也就是int的最大值
// MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 扩容操作,把就数组的数据复制到新数组,比较耗性能
elementData = Arrays.copyOf(elementData, newCapacity);
}
2.add(int index, E element)方法。指定位置插入元素。
public void add(int index, E element) {
// 检查index是否在数组范围
rangeCheckForAdd(index);
// 确保数组有足够的容量
ensureCapacityInternal(size + 1);
// index位置后面的元素往后移动
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// index位置插入新元素
elementData[index] = element;
// ArrayList大小加一
size++;
}
private void rangeCheckForAdd(int index) {
// 如果index不在数组范围内抛出异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
3.get(int index)方法。根据index获取ArrayList元素。
public E get(int index) {
// index范围检查
rangeCheck(index);
// 直接返回数组对应元素
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
4.remove(int index)方法。根据index删除元素,返回删除的元素。
public E remove(int index) {
// 检查index范围
rangeCheck(index);
// fast-fail机制
modCount++;
// 要删除的元素
E oldValue = elementData(index);
// index后面的元素往前移动,耗性能
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 数组最后的元素赋值为null,以便垃圾回收器回收。并且size减一
elementData[--size] = null;
// 返回删除的元素,返回null表示index位置为null
return oldValue;
}
5.remove(Object o)方法。删除指定的对象。
public boolean remove(Object o) {
// 删除的对象为空
if (o == null) {
// 在数组范围内查询是否有为空的元素,有就删除
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 删除指定位置的元素
fastRemove(index);
// 数组有指定的对象,并删除完毕返回true
return true;
}
}
// 删除的对象不为空
else {
for (int index = 0; index < size; index++)
// 根据equals方法判断指定对象是否在数组中,所以如果数组里面的元素是我们自定义的对象,最好重写equals方法
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
// 数组中没有指定删除的对象返回false
return false;
}
// 快速删除元素方法,省去了判断index范围,不用返回删除的元素
private void fastRemove(int index) {
// fast-fail机制
modCount++;
// index后面的元素往前移动
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
}
6.size()方法。直接返回数组中装载元素的大小,注意这里是数组中被使用的容量大小,而不是整个数组容量的大小。
public int size() {
return size;
}
7.其他存储操作方法。
判断ArrayList中是否包含了对象是用contains方法,这个方法在主接口Collection中就有定义,实现类可以自定义实现。
public boolean contains(Object o) {
// 数组中包含对象返回true,否则返回false
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
// 如果为空,就判断size范围内是否有插入空对象
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
}
// 不为空的话遍历数组,用equals方法判断是否有相等的对象
else {
for (int i = 0; i < size; i++)
// 有相等的对象就返回index值
if (o.equals(elementData[i]))
return i;
}
// 数组里没有相等的值,返回-1
return -1;
}
ArrayList的迭代器是在内部类Itr中实现的,这样实现的好处是只要不改变元素,ArrayList可以同时提供多个迭代器给客户端遍历元素。此外,迭代器要求每次调用的remove方法之前需要先调用next方法,在源码中可以看到这种限制。
public Iterator iterator() {
return new Itr();
}
private class Itr implements Iterator {
int cursor; // 迭代器游标
int lastRet = -1; // 上一次遍历的index位置,返回-1表示没有上一次位置
int expectedModCount = modCount; // fast-fail机制,如果迭代过程中数组有变动会抛异常
public boolean hasNext() {
// 游标没有到最后返回true
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
// 检测数组是否有变动
checkForComodification();
int i = cursor;
// 如果游标大于等于数组装载元素大小则抛出异常
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
// 如果游标大于等于数组容量则抛出异常
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 游标加1
cursor = i + 1;
// 返回当前遍历的元素
return (E) elementData[lastRet = i];
}
public void remove() {
// 确保调用remove方法之前要先调用next方法
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
// 删了元素的话lastRet为-1,要再次删除的话就要先调用next方法了
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
// 检查数组是否有改变
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ArrayList的内部实现是数组,在使用过程中,我们一般不用考虑ArrayList的容量大小,因为它会自动扩容,但是扩容不是无限扩容的。数组的好处是方便随机访问每个元素,也可以提供“先进先出”的特性。从上文的分析中,可以对ArrayList做个总结: