一、概述
ArrayList是List接口的一个实现类,是非线程安全的。
二、类头
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList类是个泛型类,继承自AbstractList泛型类,实现了list、RandomAccess、Cloneable跟Serializable接口。
其中RandomAccess接口是一个标记接口,表示该类支持快速随机访问,ArrayList实现该接口说明,通过
for (int i=0, i<list.size(); i++)list.get(i);的运行速度要快于以下循环:
for (Iterator i=list.iterator(); i.hasNext(); ) i.next();(本机测试相差两倍左右,List要足够大)
Cloneable也是一个标记接口,表示该类可以被拷贝。最后的Serializable接口表示可以被序列化。
三、属性
private static final long serialVersionUID = 8683452581122892189L;//可序列化编号
private transient Object[] elementData;//队列是通过数组来实现的,所以查找快速更新快,添加删除慢,队列中存放的数据(引用)这里使用了transient 关键字,作用是防止被序列化,这里有个问题,声明为transient为什么还能序列化成功呢?答案很简单ArrayList重写了writeObject方法,如果声明该方法,它将会被ObjectOutputStream调用而不是默认的序列化进程。它们既不存在于java.lang.Object,也没有在Serializable中声明。而是在ObjectOutputStream中,如何使用它们的呢?ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为private以至于供ObjectOutputStream来使用。为什么要这么做呢?比较靠谱的原因是:“ArrayList是会开辟多余空间来保存数据的,而系列化和反序列化这些没有存放数据的空间是要消耗更多资源的,所以ArrayList的数组就声明为transient,告诉虚拟机这个你别管,我自己来处理,然后就自己实现write/readObject方法,仅仅系列化已经存放的数据。”
private int size;//队列大小,标示队列的已存放数据的大小,小于等于elementData.length
四.内部常用方法
三种构造器:
public ArrayList(int initialCapacity)指定大小
public ArrayList() 不指定大小,默认为10
public ArrayList(Collection<? extends E> c)通过已知的集合来初始化
常用的方法:
1、乱七八糟方法
public void trimToSize() {//方法作用类似String.trim方法,去掉无用队列中没有使用的数据modCount++;//父类提供的属性值默认为0,用来记录对队列的修改次数,作用是判断是否存在并发修改,
进而抛出ConcurrentModificationException,从而保证Iterator遍历或者反序列化的时候不会因改变队列而产生少值问题,避免潜在bug。
int oldCapacity = elementData.length;
if (size < oldCapacity) {
elementData = Arrays.copyOf(elementData, size);
}
}
public void ensureCapacity(int minCapacity)//对以后队列进行扩容,如果原数组大小小于该值,对已有数组进行扩容一次,大小为原来的1.5倍,不够为minCapacity大小
public boolean contains(Object o) {//队列是否包含该对象,传人null也有可能找到
return indexOf(o) >= 0;//
}
public int indexOf(Object o) {//返回该对象所在位置,找到返回0-size-1数字,没有返回-1
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]))//调用的是equals方法,默认比较的是引用,使用的时候要注意是否是想要比较引用来判断对象相同,否则容易出现问题。
return i;
}
return -1;
}
public Object[] toArray() {//转化成数组
return Arrays.copyOf(elementData, size);
}
public void clear() {//清空队列
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null; // 引用清空,便于垃圾回收
size = 0;//长度变为0
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{//序列化时使用,自己重写
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);//写入有效值
if (modCount != expectedModCount) {//用来判断写入过程中原队列是否被修改
throw new ConcurrentModificationException();
}
}
public Object clone()//注意ArrayList提供的克隆方法是影子克隆即浅克隆,引用值还是指向原对象,使用时要小心
2、查找方法:
public E get(int index) {//得到指定位置的数据
RangeCheck(index);//之前先做范围验证,防止返回elementData中的无效数据,
return (E) elementData[index];//数组实现查找快
}
3、 添加方法:
public boolean add(E e) {//添加一条记录
ensureCapacity(size + 1); // 首先扩容,保证有数组有足够的空间来容纳新数据,这里存在效率问题,可以预估计大小的数据直接声明创建好,否则容易重复多次拷贝
elementData[size++] = e;//没有判断是否为null,所以ArrayList可以存放null值
return true;
}
public boolean addAll(Collection<? extends E> c) {//添加一个集合,大数据的时候比单个添加效率高
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacity(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);//调用本地方法,快速复制,自己进行数组赋值的时候可以直接采用这个方法效率高。可以自己拷贝自己没有问题
size += numNew;
return numNew != 0;
}
4、 删除提供了两种方法:通过通过索引删除和引用删除实现如下:
public E remove(int index) {//删除指定索引元素,从0开始
RangeCheck(index);//范围正确性验证
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)//判断是否是删除最后节点
System.arraycopy(elementData, index+1, elementData, index,
numMoved);//数组赋值,把index+1之后的数据赋值到从index开始的长度是numMoved的数组里面去,相当于把删除节点之后的数据整体后移一位,所以直接不适用迭代器
遍历的时候,如果直接删除,会丢数据甚至有可能产生越界异常。
elementData[--size] = null; // 引用清空,便于垃圾回收
return oldValue;//返回被删除节点值
}
public boolean remove(Object o) {//通过引用删除
if (o == null) {//传入null
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);//快速删除就是没有做范围判断,直接删除,内部私有方法,不对外提供,API提供的都是安全的方法,有异常也会抛出,
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {//通过equals判断
fastRemove(index);
return true;
}
}
return false;
}
五、总结
通过阅读源码,知道 ArrayList相当于动态数组 ,所以查找特别快,但是修改删除比较耗时,需要进行数组扩容 ,最好开始使用的时候创建合适大小的数组,以后超范围了ArrayList会自己进行扩充,不用担心越界问题。
ArrayList允许添加null对象,使用contains方法时要注意会调用equals进行判断,默认比较的是引用。
对ArrayList进行clone的时候要注意,是浅拷贝。
学习modCount的使用,数组复制的时候多使用System.arrayCopy()方法速度快。