ArrayList是java最常见的一个集合,是经常要用到的一个集合,这里详细讲一下。
ArrayList是基于数组实现的一个List类,封装了一个动态的,允许再分配的Object数组。
这里重点关注ArrayList的四个方面:
1.能不能存储空值 -》能
2.是否线程安全
3.集合的动态扩容对性能的影响
4.增删改查的效率
先从源码上看ArrayList的增删改查方法
添加操作
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
第二行的方法是确保ArrayList大小足够,给ArrayList扩容的方法,后面再分析。
ArrayList是直接将元素添加到集合中,非常方便
插入操作
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是否超出索引,超出则抛出异常,然后进行扩容。
值得关注的是,ArrayList插入元素前需要将数组进行一遍复制,然后将element放在数组index位置的后面。
删除操作
ArrayList中有两种删除操作,一种是通过index删除,一种是通过object删除。
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;
}
通过下标删除,操作与插入操作类似,对index判断,然后将指定元素后面的数组进行复制,让整体向前移动一个位置,且将最后一个元素置为null,便于gc去回收。
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;
}
通过对象删除,是遍历一遍数组,找到与object相等的元素,进行删除,看得出效率还是慢的。
fastmove()方法是执行跟上面的删除方法是一样的操作,将指定元素后的数组整体向前移动一个位置,且将最后一个元素置为null。
并且ArrayList能够存储重复值,也就是说,如果ArrayList有多个与object相等的元素,remove会删除index最小的元素。
查找操作
ArrayList可以通过index查找,也可以通过object查找
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
通过index查找是非常快的一个操作,只需要先检查index,然后返回值,这是ArrayList的优点。
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;
}
通过object查找跟前面的类似,遍历整个数组,找到首个等于object的元素并返回。
优缺点总结
优点:
1.ArrayList的get()查找操作只需要对index判断,然后返回值,效率很快
缺点:
1.删除操作在极端情况下需要移动整个数组,效率比较低
2.插入操作在极端情况下需要移动整个数组,效率比较低
ArrayList和Vector的比较
线程安全
两者是最大区别是:ArrayList是线程不安全的,Vector是线程安全的。
要使ArrayList成为线程安全的,可以使用Collections类提供的synchronized()方法,
ArrayList a = Collections.synchronizedList(new ArrayList<>());
这样可以使得ArrayList 成为线程安全的。
扩容
ArrayList扩容方法
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
首先将ArrayList的新容量等于旧容量的1.5倍,判断大小是否超过Integer的最大值,然后将数组复制一遍。
至于为什么是1.5倍
1.扩容太大必然浪费资源
2.扩容太小就得多次扩容,每次都得复制数组,影响性能
1.5倍的扩容应该是一个权衡利弊后合适的值
Vector的扩容方法
Vector初始化若不指定参数,大小会默认为10
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
Vector在初始化时可以定义一个容量因子capacityIncrement,每次扩容都增加一个capacityIncrement,如果没有定义容量因子,vector的容量每次会增加一倍。
总结
推荐使用ArrayList,而不推荐Vector,书上是这么写的。
写这篇文章参考了博客园:‘五月的仓颉’这位大神的文章,源码上有一点区别,应该是java版本的不同。