本篇主要介绍ArrayList的用法和源码分析,基于jdk1.8,先从List接口开始。
List
List接口定义了如下方法:
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
void add(int index, E element);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
boolean remove(Object o);
E remove(int index);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
E get(int index);
E set(int index, E element);
int indexOf(Object o);
int lastIndexOf(Object o);
List<E> subList(int fromIndex, int toIndex);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
乍一看,这么多方法。其实很多方法是同样的功能,方法重载而已。
接下来逐个介绍下List定义的方法。
- size 获得List元素的个数
- isEmpty 判断List是否为空
- contains/containsAll 判断List中是否有该元素,或者有该集合中的所有元素
- iterator 获得迭代器对象用于迭代
- toArray 将List转换成数组
- add/addAll 添加元素至List中,默认直接添加到最后,也可以选择指定的位置,还可以添加整个集合
- remove/removeAll 删除元素,可以根据元素删除,也可以根据索引删除,还可以根据集合删除
- retainAll 取与目标集合的交集
- clear 清空List的所有元素
- get 根据索引获得元素
- set 覆盖索引处的元素
- indexOf 获得该元素的索引
- lastIndexOf 获得该元素的索引(从后往前)
- subList 获得子List根据start 和 end
- listIterator 获得ListIterator对象
方法名言简意赅,基本上都可以从方法名知道方法的目的。
接下来分析List的常用实现类:
ArrayList
LinkedList
Vector
本篇介绍ArrayList
ArrayList类图
在我的理解中,ArrayList是一个封装的数组,提供了一些便利的方法供使用者使用,规避了使用原生数组的风险。
ArrayList的定义
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
继承了AbstractList即成为了List,就要实现定义的所有方法。
实现了RandomAccess接口 就是提供了随机访问能力,可以通过下标获得指定元素
实现了Cloneable接口 代表是可克隆的,需要实现clone方法
实现了Serializable接口 代表ArrayList是可序列化的
下面介绍下ArrayList的主要方法
添加元素
boolean add(E e)
先附上源码
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
此方法的执行逻辑:
- 判断数组长度是否够,不够则扩容。默认扩容1.5倍
- elementData[size++] = e;将新元素添加进去。
private void ensureCapacityInternal(int minCapacity) {
//是否是空List,是则使用初始容量扩容
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //增加修改次数
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容1.5倍,采用位运算
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);//最大容量就是Integer的最大值
// minCapacity is usually close to size, so this is a win:
//采用Arrays的copyOf进行深拷贝,其中调用的本地方法System.arraycopy,此方法是在内存中操作因此速度会很快。
elementData = Arrays.copyOf(elementData, newCapacity);
//至此扩容结束
}
void add(int index, E element)方法稍有不同
- 首先检查index是否合法
- 扩容
- 将新元素插入指定位置
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//将index -> (size -1)的元素都往后移动一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
boolean addAll(Collection<? extends E> c)
boolean addAll(int index, Collection<? extends E> c)
这两个方法和上面的add大同小异,第一步都是判断容量,并扩容。
容量大小从1变为c的length,elementData[index] = element;赋值也变为数组拷贝,
直接上代码,秒懂。
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)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
add总结
- 无论是哪种add,都需要先执行方法 ensureCapacityInternal(size + 1) 进行容量判断及扩容,确保之后的操作不会产生数组越界。
- 建议在我们日常工作时,如果大概知道元素的数量,可以在初始化的时候指定大小,这样可以减少扩容的次数,提升性能。
删除元素
E remove(int index)
- 检查是否索引不正确。
- 计算移动的元素的个数,数组往前移动。
- 将之前第size位置的元素置空,返回被删除的元素。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
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
return oldValue;
}
boolean remove(Object o)
- 区分要删除的元素是null还是非null。
- 找到该元素的索引,执行上面方法逻辑的2、3步。
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++;
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
}
这种方法依赖元素的equals方法,循环遍历数组,因为ArrayList是允许null元素的,所以无论是使用if (o.equals(elementData[index])) 还是 if (elementData[index].equals(o)) 均可能产生空指针,所以单独对null进行处理,逻辑都是一样的。
奇怪的是这个fastRemove方法,原本以为会有些特殊处理,结果发现代码和上面remove(int index)中的一模一样,为什么上面的remove中不调用这个fastRemove呢?难道写两个remove方法的不是同一人?逻辑不影响,只是代码冗余了一点。
boolean removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
删除存在于目标集合的方法,使用了batchRemove(Collection<?> c, boolean complement)这个方法,这个方法作了封装,在retainAll(Collection<?> c)取并集这个方法也有使用。
retainAll中是这样使用的:
return batchRemove(c, true);
和我们的removeAll只差第二个参数boolean complement,remove是false,retail是true,那到底是什么意思呢?complement的原意的补充,在这里我理解为保留,remove就不保留,retail就保留,接着我们分析batchRemove这个方法。
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
定义了 r 和 w两个int,r用于遍历原来的数组,w的意思是新的数组的size。
假如定义2个数组用于举例,
数组1,五个元素 1,2,3,4,5
数组2,五个元素 3,4,6,7,8
数组1调用removeAll(数组2)
此时进入batchRemove,我们来一步一步走看r,w如何变化
此时complement是false,即数组2中没有数组1中第r个元素才满足if条件
- r = 0, w = 0, elementData[r] = 1, c.contains(elementData[r]) = false.
执行elementData[w++] = elementData[r],
数组1:1,2,3,4,5 - r = 1, w = 1, elementData[r] = 2, c.contains(elementData[r]) = false.
执行elementData[w++] = elementData[r],
数组1:1,2,3,4,5 - r = 2, w = 2, elementData[r] = 3, c.contains(elementData[r]) = true.
此时if条件不满足了,w不自增了,代表elementData[r]要不存在与新数组中,要被删除,所以跳过了。 - r = 3, w = 2, elementData[r] = 4, c.contains(elementData[r]) = true.
if条件依旧不满足,w不自增,此元素也是要删除。 - r = 4, w = 2, elementData[r] = 5, c.contains(elementData[r]) = false.
执行elementData[w++] = elementData[r],
数组1:1,2,5,4,5
此时for循环结束,elementData数组目前是这样的,接着执行finally中的代码,
首先执行
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
我们的情况是 r=size,那什么时候r会不等于size呢,jdk中写了注释,就是在if判断时,调用数组2的contains方法,可能会抛空指针等异常。这时数组还没有遍历完,那r肯定是小于size的。
那没判断的那些数据还要不要处理?保守起见jdk还是会将他保存在数组中,因为最终w是作为新的size,所以w加上了没处理过的个数size - r。
接着执行下面一段
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
这段代码意图很清晰,w因为是新的size值,所以将w及其之后的都置位null,增加修改次数,
给size赋予新值之后就结束了。
remove总结
- 删除元素尽量使用指定下标的方法,性能好。
- 每次删除都会进行数组移动(除非删除最后一位),如果频繁删除元素,请使用LinkedList
boolean contains(Object o)
contains 底层调用的是indexOf方法
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
int indexOf(Object o)
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
就是循环遍历,一个个比对,有则返回对应下标,无则返回-1
对应的lastIndexOf是从最后往前遍历。
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
E get(int index)
rangeCheck(index);
return elementData(index);
先检查index,再返回数组对应元素。
E set(int index, E element)
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
先检查index,在覆盖index的元素,返回旧元素。
void clear()
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
增加修改次数,将所有元素置空,size设置为0,需要注意的是,数组的大小是没有变化的。
int size()
size方法就是直接将size变量直接返回
public int size() {
return size;
}
boolean isEmpty()
判断size是否等于0
public boolean isEmpty() {
return size == 0;
}
迭代器
Iterator<E> iterator()
public Iterator<E> iterator() {
return new Itr();
}
如代码所示,创建了一个Itr对象并返回,接下来看Itr类的定义
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
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();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
首先介绍下三个成员变量
- cursor,代表下一个访问元素的在elementData数组中的索引,初始值是0。
- lastRet,代表上一个访问元素在elementData数组中的索引,初始值是-1。
- expectedModCount,预期的modcount的值,在Itr对象创建的时候从父类ArrayList的modcount获得,用于判断在使用该对象迭代时,有么有对数组进行修改,有则会抛出ConcurrentModificationException。
平常常用的迭代器方法
hasNext()
就是判断当前索引是否等于size。
E next()
首先检查list是否被修改过,expectedModCount是在创建对象时就获得了,如果在之后对list进行了其他修改操作的话,modCount就会增加,就会抛出ConcurrentModificationException。
没有异常就按下表返回坐标,cursor自增,lastRet也自增。
void remove()
首先判断是否能删除,若能则调用父类的remove方法,删除元素,接着会更新cursor和lastRet。
最重要的是会更新expectedModCount,此时调用了父类的remove方法,会使modCount+1,所以更新了 expectedModCount,让后续的检查不会抛异常。
ListIterator<E> listIterator
listIterator和iterator一样,只不过listIterator有更多的方法,相当于iterator的加强版。
定义如下
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public E previous() {
checkForComodification();
try {
int i = cursor - 1;
E previous = get(i);
lastRet = cursor = i;
return previous;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor-1;
}
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.set(lastRet, e);
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
AbstractList.this.add(i, e);
lastRet = -1;
cursor = i + 1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
没有什么需要注意的地方,基本的方法都从Iterator中继承过来并添加一些方法。
List<E> subList(int fromIndex, int toIndex)
这又是一个内部类
class SubList<E> extends AbstractList<E> {
private final AbstractList<E> l;
private final int offset;
private int size;
SubList(AbstractList<E> list, int fromIndex, int toIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > list.size())
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
l = list;
offset = fromIndex;
size = toIndex - fromIndex;
this.modCount = l.modCount;
}
。
。
。
}
持有了父类的引用,内部的所有方法都是通过父类的这个引用去完成的,
所以需要注意的是,subList不是返回一个新的List,还是原来的引用,所以改变subList的数据,原有的数据也会更改。
终于把基本的方法都介绍完了,从源码的角度分析了所有的方法,感觉对ArrayList知根知底了,用它时肯定会更加得心应手了,看了源码才有原来实现都这么简单啊这样的感觉,不过从中也学习到了大牛规范的代码风格,良好的结构,可读性很高。下篇分析List的另一个实现类,LinkedList。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。