概述
ArrayList 基于数组实现,是一个动态数组,其容量能自然增长(1.5倍增长)。
不是线程安全,你可以使用Collection.synchronizedList方法将该列表包装起来,以防止意外对列表进行不同步的访问。也可以使用concurrent并发包下的CopyOnWriteArrayList类。
java 1.6API对其解释
返回指定列表支持的同步(线程安全的)列表。为了保证按顺序访问,必须通过返回的列表完成所有对底层实现列表的访问。
在返回的列表上进行迭代时,用户必须手工在返回的列表上进行同步:
List list = Collections.synchronizedList(new ArrayList());
...
synchronized(list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
不遵从此建议将导致无法确定的行为。
如果指定列表是可序列化的,则返回的列表也将是可序列化的。
ArrayList实现了 List,RandomAccess,Cloneable,Serialiable 接口
RandomAccess接口,支持随机访问,实际上就是通过下标序号进行快速访问。实际上,实现此接口的List使用 for (int i=0, n=list.size(); i < n; i++) 这种方式迭代的速度会比用for(int i : list)会快一点
ArrayList实现(JDK1.7)
ArrayList中定义了四个私有属性:
private static final int DEFAULT_CAPACITY = 10; //默认容量
private static final Object[] EMPTY_ELEMENTDATA = {}; //一个空数组,当用户指定了0为容量时,返回该空数组
private transient Object[] elementData; //实际存放数据的数组
private int size; //实际存放数据的大小
构造方法:
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//构造一个新数组,指定容量
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
super();
//默认等于空的数组
this.elementData = EMPTY_ELEMENTDATA;
}
//此方法的Collection是指集合类只要实现了Collection接口 都能重新转换为ArrayList(List接口已经继承了Collection接口)
public ArrayList(Collection<? extends E> c) {
//转换为数组
elementData = c.toArray();
size = elementData.length;
if (elementData.getClass() != Object[].class)
//调用native方法 快速构造数组
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
ArrayList增加操作
'''
//将当前容量调整为实际个数
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = Arrays.copyOf(elementData, size);
}
}
public boolean add(E e) {
//此方法的关键是grow函数,增加元素是一个一个添加 所以size+1传入进去
ensureCapacityInternal(size + 1);
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) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//默认增加为原大小的1.5倍 oldCapacity>>1是把数转换为二进制 并向右移动一位 效果相当于 oldCapacity/2
int newCapacity = oldCapacity + (oldCapacity >> 1);
//判断增加后的大小够不够,够了就直接使用newCapacity创建新数组
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)//如果增加后的大小比规定的最大size还大 则调用hugeCapacity方法
newCapacity = hugeCapacity(minCapacity);
//正常情况下 新的数组大小都为以前的1.5倍
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
容量扩大,调用的是Arrays.copyOf(..,..);这个方法,虽然这个方法是native方法,源码里面是创建一个新的数组,然后将旧数组上的数组copy到新数组,这是一个很大的消耗。如果放在程序中,我们最好能够预计其大小,避免重复申请内存。
注意:
1.在jdk1.6中
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
很明显 这里的数组扩容没有使用位运算,而是直接使用除法和乘法,从效率上来看,jdk1.7 的ArrayList 会比 jdk1.6快一点(位运算更接近系统底层,有兴趣的同学 可以百度)
2.jdk1.6中没有定义MAX_ARRAY_SIZE的大小,所以无法做判断,这也是1.7中改进的地方。
例子
我们在15 16行打上断点
到达15行 还未运行完15行时 list中只有4个数据 所以size为4 因为刚开始定义了5为list的初始大小,则下标为4的为null
当运行完15行 到达16行时,很明显list已经满了
运行完16行后,list扩容为7 (5*1.5 省略小数点后面的数) 所以下标为6的为null
最后输出list.size()的大小,因为是返回list实际的大小 和elementData大小无关
说完了最复杂的增加操作,我们说删,改,查。
删除操作
//移除指定位置的数据
public E remove(int index) {
//检查是否下标越界
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
//elementData从第index+1下标开始 复制到原elementData的index下标开始 numMoved是复制的长度
//numMoved已经定义好了 是从要移除的下标号后面还剩余的数组长度
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//数组最后一个数就为null了
elementData[--size] = null;
//返回移除后的数据
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//移除指定数据
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;
}
//其实这个方法和remove(int index) 里面的方法如出一辙
//可能开发人员没有重复把这个方法运用到remove(int index)里,
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;
}
以上这些 就很好的说明了 为什么ArrayList增和删的效率不高了 都是要重新给数组赋值,或者新new一个数组接收更改后的旧数组,这样对内存的消耗会很大。
修改和删除
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
这个应该很简单了吧 我就不给注释了。
ArrayList扩展
我们在eclipse中 ctrl+t 查看实现list接口的类有哪些
这其中我们所了解的恐怕只有LinkedList和Vector了
这里大致说一下这两个集合类
LinkedList 底层是链表结构,而且是双链表,也可以当作堆栈,队列或双端队列进行操作。 特点是:查询效率低,增删效率高
Vector 底层也是用数组实现的,里面大部分方法都被声明了synchronized关键字,所以说是线程安全的集合,就是因为synchronized关键字的存在,这个集合本身就是很重量级,几乎很少用到。
关于线程安全和不安全的问题,因为我们是程序员嘛,当然会有办法外部控制这个操作,所以没必要纠结用哪个集合。但是如果有现成的类使用,就不要重复造轮子了。