ArrayList
ArrayList就是动态数组,就是array的复杂版本。它可以动态的增加或者减少元素,可以在使用的过程中动态扩容,要注意,ArrayList是线程不安全的,它并没有使用Synchronized,CAS,Lock,一般只在单线程中使用,多线程中可以使用Vector或者CopyOnWriteArrayList。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList继承了AbstractList,实现了List,提供了增加,查找,删除,遍历等功能。
ArrayList实现了RandomAccess接口,提供了随机访问的功能。我们可以通过索引快速定位到元素对象。
ArrayList实现了Cloneable接口,数组能被克隆,实现了序列化接口,代表ArrayList支持序列化。
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 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;
}
}
默认构造一个空的Object数组,也可以构造一个指定初始容量大小的空的Object数组,如果容量大小<0,直接抛出异常,如果容量大小=0,构造一个空的Object数组。也可以构造一个指定Collection元素的列表。
查找
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
首先会判断输入的索引是否越界,如果索引>=size,直接抛出数组越界异常,否则,直接返回指定位置上的元素。如果索引<0,则由系统直接抛出异常。
替换
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
在数组索引不越界的情况下(index >= 0 && index < size),用新值覆盖原索引位置上的旧值,并且返回该旧值。
增加
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, 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);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
每次加入元素的时候都会检查数组的容量是否足够,如果数组为空的话,那么就给数组的容量大小设为10,如果添加元素后>当前数组的长度,就要进行扩容操作(将新数组容量大小变为原数组容量大小的1.5倍;如果新数组容量<minCapacity(实际元素的个数),那么就把minCapacity作为新数组的容量;如果新数组的容量>数组允许的最大容量(Integer.Max_value-8),若minCapacity>Integer.Max_value-8,新数组容量=Integer.Max_value,若minCapacity<=Integer.Max_value-8,新数组容量=Integer.Max_value-8,调用copyof方法将原数组拷贝到新数组中,copyof是非常消耗性能的操作,所以在可预估容量大小的时候,尽量在构造方法中给它赋一个初始值,以减少频繁扩容)。size代表实际存放元素个数,把元素放到size++位置上,直接返回true。
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
判断(index >0 && index < size),检查加入元素后数组容量是否足够,容量不够进行扩容。从index开始及后面的元素都往后移一位,将元素放到index位置上,size大小增加1。
删除
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;
}
检查索引是否越界(size >= 0 && size < size),修改modcount,计算要移动的元素个数,使用arraycopy方法把index后面的元素全部往前移动一位,--size位置处置为空(index是从0开始计算的,--size其实就等于当前数组存储元素个数-1,即为存储的最大索引),便于垃圾回收,返回该索引位置上的旧值。
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
}
删除数组中首次出现的元素,因为ArrayList中允许存放null,所以分为两种情况进行处理,删除null和非null,过程和上面类似,删除成功直接返回true,否则返回false,在fastRemove方法中并没有进行index越界判断,这是因为对elementdata数组进行遍历,找到了要删除的元素其实就代表index不会越界。
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
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++) {
elementData[i] = null;
}
size = newSize;
}
删除指定范围内的元素,计算要移动元素的个数,将toindex开始的元素向前移动到fromindex,toindex开始及之后的元素置为空,修改size大小。
压缩
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
通过这个方法我们可以把数组的容量调整为当前保存元素个数的实际大小。在size<数组容量时,如果数组的大小为空,创建一个空的Object数组,否则,把数组容量变为当前size大小,节约空间。
判断元素是否在数组中
public boolean contains(Object o) {
return indexOf(o) >= 0; //判断数组中是否包含元素o
}
public int indexOf(Object o) {
if (o == null) { //要查找的元素为null,需要从头开始遍历,找到返回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
}
清空
public void clear() {
modCount++;
// 把数组中的元素置为空,size置为0
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
遍历
public Iterator<E> iterator() {
return new Itr(); //返回一个itr对象
}
private class Itr implements Iterator<E> {
int cursor; // 标记遍历到哪个元素
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
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();
}
}
}
通过hasNext方法判断数组中是否还有元素,如果cursor==size,说明数组已经遍历完了。使用Next方法获取元素,判断在遍历的过程中是否进行了add,remove等操作,如果modCount !=expectedModCount,直接抛出当前修改异常;判断索引是否>=size,是否>=数组的容量;返回该索引处的元素,cursor ++。在遍历的过程中可以进行删除操作,因为此时会进行赋值expectedModCount==modCount,所以不会抛出异常。
总结
ArrayList是基于数组实现的,可以动态扩容,但是数组最大容量为Integer.Max_value-8.
通过索引可以直接定位到元素,所以查找效率高,但是删除时,需要移动很多元素,插入元素到指定位置的时候,需要将该位置以及后面的元素全部往后移动一位,所以删除和插入到指定位置的效率比较低.
要尽量使用迭代遍历进行删除,此时不会抛出ConcurrentModificationException.
remove(index)比remove(o)效率高,因为remove(o)要遍历数组
Vector
在多线程中,可以使用Vector来代替ArrayList,Vector和ArrayList极为类似,Vector有4个不同的构造方法,默认创建一个容量为10的数组,只包含容量的构造方法则将容量增长设为0。在进行容量扩容时,首先看一下构造参数中的CapacityIncrement是否>0,如果CapacityIncrement=0,就将新容量变为原数组容量的两倍,如果CapacityIncrement>0,新容量 = 原数组容量+CapacityIncrement。很多方法都用Synchronized进行了修饰,保证线程安全。但Vector已经很少使用了。
---------------------------------------------------------------------------------------------------- 希望5月1日是个值得纪念的日子,在今天写下了人生的第一篇博客。