ArrayList是我们使用得最多的一个集合类之一
一般用来做包装DTO到view层来显示数据.
( 支持随机访问)。
1.存储
ArrayList使用一个Object的数组存储元素。
private transient Object elementData[];
ArrayList实现了java.io.Serializable接口,这儿的transient标示这个属性不需要自动序列化。下面会在writeObject()方法中详细讲解为什么要这样作。
2.add和remove
public boolean add(Object o) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = o;
return true;
}
注意这儿的ensureCapacity()方法,它的作用是保证elementData数组的长度可以容纳一个新元素。在“自动变长机制”中将详细讲解。
public Object remove(int index) {
RangeCheck(index);
modCount++;
Object oldValue = elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
RangeCheck()的作用是进行边界检查。由于ArrayList采用一个对象数组存储元素,所以在删除一个元素时需要把后面的元素前移。删除一个元素时只是把该元素在elementData数组中的引用置为null,具体的对象的销毁由垃圾收集器负责。
modCount的作用将在下面的“iterator()中的同步”中说明。
注:在前移时使用了System提供的一个实用方法:arraycopy(),在本例中可以看出System.arraycopy()方法可以对同一个数组进行操作,这个方法是一个native方法,如果对同一个数组进行操作时,会首先把从源部分拷贝到一个临时数组,在把临时数组的元素拷贝到目标位置。
3.自动变长机制
在实例化一个ArrayList时,你可以指定一个初始容量。这个容量就是elementData数组的初始长度。如果你使用:
ArrayList list = new ArrayList();
则使用缺省的容量:10。
public ArrayList() {
this(10);
}
ArrayList提供了四种add()方法,
public boolean add(Object o)
public void add(int index, Object element)
public boolean addAll(Collection c)
public boolean addAll(int index, Collection c)
在每一种add()方法中,都首先调用了一个ensureCapacity(int miniCapacity)方法,这个方法保证elementData数组的长度不小于miniCapacity。ArrayList的自动变长机制就是在这个方法中实现的。
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;
elementData = new Object[newCapacity];
System.arraycopy(oldData, 0, elementData, 0, size);
}
}
从这个方法实现中可以看出ArrayList每次扩容,都扩大到原来大小的1.5倍。并且是生成一个新的数组,废弃原来老的数组.
每种add()方法的实现都大同小异,下面给出add(Object)方法的实现:
public boolean add(Object o) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = o;
return true;
}
4.iterator()中的同步
在父类AbstractList中定义了一个int型的属性:modCount,记录了ArrayList结构性变化的次数。
protected transient int modCount = 0;
在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。
注:add()及addAll()方法的modCount的值是在其中调用的ensureCapacity()方法中增加的。
AbstractList中的iterator()方法(ArrayList直接继承了这个方法)使用了一个私有内部成员类Itr,生成一个Itr对象(Iterator接口)返回:
public Iterator iterator() {
return new Itr();
}
Itr实现了Iterator()接口,其中也定义了一个int型的属性:expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值。
int expectedModCount = modCount;
注:内部成员类Itr也是ArrayList类的一个成员,它可以访问所有的AbstractList的属性和方法。理解了这一点,Itr类的实现就容易理解了。
在Itr.hasNext()方法中:
public boolean hasNext() {
return cursor != size();
}
调用了AbstractList的size()方法,比较当前光标位置是否越界。
在Itr.next()方法中,Itr也调用了定义在AbstractList中的get(int)方法,返回当前光标处的元素:
public Object next() {
try {
Object next = get(cursor);
checkForComodification();
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
注意,在next()方法中调用了checkForComodification()方法,进行对修改的同步检查:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
现在对modCount和expectedModCount的作用应该非常清楚了。在对一个集合对象进行跌代操作的同时,并不限制对集合对象的元素进行操作,这些操作包括一些可能引起跌代错误的add()或remove()等危险操作。在AbstractList中,使用了一个简单的机制来规避这些风险。这就是modCount和expectedModCount的作用所在。
5.序列化支持
ArrayList实现了java.io.Serializable接口,所以ArrayList对象可以序列化到持久存储介质中。ArrayList的主要属性定义如下:
private static final long serialVersionUID = 8683452581122892189L;
private transient Object elementData[];
private int size;
可以看出serialVersionUID和size都将自动序列化到介质中,但elementData数组对象却定义为transient了。也就是说ArrayList中的所有这些元素都不会自动系列化到介质中。为什么要这样实现?因为elementData数组中存储的“元素”其实仅是对这些元素的一个引用,并不是真正的对象,序列化一个对象的引用是毫无意义的,因为序列化是为了反序列化,当你反序列化时,这些对象的引用已经不可能指向原来的对象了。所以在这儿需要手工的对ArrayList的元素进行序列化操作。这就是writeObject()的作用。
private synchronized void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
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]);
}
这样元素数组elementData中的所以元素对象就可以正确地序列化到存储介质了。
对应的readObject()也按照writeObject()方法的顺序从输入流中读取:
private synchronized void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in array length and allocate array
int arrayLength = s.readInt();
elementData = new Object[arrayLength];
// Read in all elements in the proper order.
for (int i=0; i<size; i++)
elementData[i] = s.readObject();
}
ArrayList继承了AbstractList类,实现了List,RandomAccess,Cloneable接口
Java代码
1. public class ArrayList<E> extends AbstractList<E>
2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable
内部结构是一个Object类型的数组
Java代码
1. private transient Object[] elementData;
ArrayList的大小,也就是元素个数
Java代码
1. private int size;
下面是几个构造函数:
1.自定义初始化容量的构造函数:
Java代码
1. public ArrayList(int initialCapacity) {
2. super();
3. if (initialCapacity < 0)//初始化容量不能小于0,抛出IllegalArgumentException异常
4. throw new IllegalArgumentException("Illegal Capacity: "+
5. initialCapacity);
6. this.elementData = new Object[initialCapacity];//根据参数初始化一个数组,底层是个object数组
7. }
2.默认构造函数:
调用上面的构造函数,默认初始化内部数组大小为10
Java代码
1. public ArrayList() {
2. s(10);//默认初始容量是10
3. }
3.collection转换的构造函数:
Java代码
1. public ArrayList(Collection<? extends E> c) {
2. elementData = c.toArray();//调用toArray()方法把collection转换成数组
3. size = elementData.length;//把数组的长度赋值给ArrayList的size属性
4. // c.toArray might (incorrectly) not return Object[] (see 6260652)
5. if (elementData.getClass() != Object[].class)
6. elementData = Arrays.copyOf(elementData, size, Object[].class);
7. }
返回ArrayList的大小:
Java代码
1. public int size() {
2. urn size;
3. }
判断是否为空:
Java代码
1. public boolean isEmpty() {
2. urn size == 0;//就是看当前size是否为0
3. }
找出一个元素第一次出现的下标:
Java代码
1. public int indexOf(Object o) {
2. //由于数组的下标是从0开始的,所以判断是否存在只要大于0就可以了
3. if (o == null) {
4. for (int i = 0; i < size; i++)//注意这里是size属性而不是elementData的length
5. if (elementData[i]==null)
6. return i;
7. } else {
8. for (int i = 0; i < size; i++)
9. if (o.equals(elementData[i]))//equals方法来判断
10. return i;
11. }
12. return -1;//没有的话返回-1
13. }
判断是否包含一个元素:
Java代码
1. public boolean contains(Object o) {
2. urn indexOf(o) >= 0;//不存在是-1
3. }
元素最后一次出现的下标:
Java代码
1. public int lastIndexOf(Object o) {
2. if (o == null) {
3. for (int i = size-1; i >= 0; i--)//注意这里的i是等于数组长度减1,从数组的最后一位开始
4. if (elementData[i]==null)
5. return i;
6. } else {
7. for (int i = size-1; i >= 0; i--)
8. if (o.equals(elementData[i]))
9. return i;
10. }
11. return -1;
12. }
重新分配ArrayList空间为元素多少的真实大小:
注意size一般和内部结构的数组长度是不一样的,通过上面的构造函数我们知道内部数组初始化容量是10,
而size是在add()方法后才加一,这个方法是保证列表的大小和内部数组的大小一致
Java代码
1. public void trimToSize() {
2. modCount++;
3. int oldCapacity = elementData.length;
4. if (size < oldCapacity) {
5. elementData = Arrays.copyOf(elementData, size);
6. }
7. }
如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数
因为每次重新分配空间都是比较消耗时间的,所以如果能预计list可能的大小
的话可以通过自己的控制ArrayList的大小来提高效率
Java代码
1. public void ensureCapacity(int minCapacity) {//注意是public类型,也就是说可以我门自己来重新分配空间
2. modCount++;
3. int oldCapacity = elementData.length;//老的容量
4. if (minCapacity > oldCapacity) {
5. Object oldData[] = elementData;//把当前数组临时保存起来
6. int newCapacity = (oldCapacity * 3)/2 + 1;//加1是为了保证oldCapacity为1或者0的情况下
7. if (newCapacity < minCapacity)//是按1.5被来增加容量的
8. newCapacity = minCapacity;
9. // minCapacity is usually close to size, so this is a win:
10. elementData = Arrays.copyOf(elementData, newCapacity);
11. }
12. }
clone一个副本:
Java代码
1. public Object clone() {
2. try {
3. ArrayList<E> v = (ArrayList<E>) super.clone();
4. v.elementData = Arrays.copyOf(elementData, size);
5. v.modCount = 0;
6. return v;
7. } catch (CloneNotSupportedException e) {
8. // this shouldn't happen, since we are Cloneable
9. throw new InternalError();
10. }
11. }
转换为数组:
Java代码
1. public Object[] toArray() {
2. return Arrays.copyOf(elementData, size);//调用Arrays.copyOf()方法
3. }
下面是转换为泛型数组:
Java代码
1. public <T> T[] toArray(T[] a) {
2. if (a.length < size)
3. // Make a new array of a's runtime type, but my contents:
4. return (T[]) Arrays.copyOf(elementData, size, a.getClass());
5. System.arraycopy(elementData, 0, a, 0, size);
6. if (a.length > size)
7. a[size] = null;
8. return a;
9. }
范围检查:臭名昭著的 IndexOutOfBoundsException异常
Java代码
1. private void RangeCheck(int index) {
2. (index >= size)//数组越界,这里没有判断小于0的情况
3. throw new IndexOutOfBoundsException(
4. ndex: "+index+", Size: "+size);
5. }
通过下标得到一个元素:
Java代码
1. public E get(int index) {
2. RangeCheck(index);//先检查是否越界
3.
4. return (E) elementData[index];//返回的是数组中的下标 }
通过下标和一个元素赋值,返回的是原先的值:
Java代码
1. public E set(int index, E element) {
2. RangeCheck(index);//先检查是否越界
3.
4. E oldValue = (E) elementData[index];//通过临时变量把当前下标的值保存
5. elementData[index] = element;//赋值
6. return oldValue;//注意返回的是当前下标的原先值
7. }
添加一个新的元素到末尾,前面说道新增方法都要先调用ensureCapacity方法:
Java代码
1. public boolean add(E e) {
2. ensureCapacity(size + 1); //大小加一 // Increments modCount!!
3. elementData[size++] = e;//size默认是0所以是从0开始赋值
4. return true;
5. }
API文档中的说明是:将指定的元素插入此列表中的指定位置。向右移动当前位于该位置的元素(如果有)以及所有后续元素(将其索引加 1)。通俗的说法是在指定位置插入元素,指定元素和后面的元素后移
这个方法和set(int index, E element)不一样,set只是把元素赋值给指定的下标同时返回下标的原先值.
add(int index, E element)的判断越界是通过元素的大小来判断的
所以如果
Java代码
1. ArrayList list=new ArrayList();
2. list.add(1, 8);
3. //报错,因为size元素大小还是0
4. //如果l
5. list.add(0,"")//就可以
如果一致add同一下标所有后续元素索引加1
如下:
Java代码
1. ArrayList list=new ArrayList();
2. list.add(0, 8);
3. list.add(0, 8);
4. list.add(0, 8);
5. System.out.println(list);
6. //结果为[8, 8, 8]
:
Java代码
1. public void add(int index, E element) {
2. if (index > size || index < 0)//判断是否越界,注意这里是以元素的个数来判断的
3. throw new IndexOutOfBoundsException(
4. "Index: "+index+", Size: "+size);
5.
6. ensureCapacity(size+1); // Increments modCount!!
7. System.arraycopy(elementData, index, elementData, index + 1,
8. size - index);
9. //源数组中位置在 srcPos 到 srcPos+length-1 之间的组件被分别复制到
10. //目标数组中的 destPos 到 destPos+length-1 位置
11. elementData[index] = element;
12. size++;//元素加一
13. }
删除指定位置的元素,返回被删除的元素,由于ArrayList采用一个对象数组存储元素,所以在删除一个元素时需要把后面的元素前移。删除一个元素时只是把该元素在elementData数组中的引用置为null,具体的对象的销毁由垃圾收集器负责
Java代码
1. public E remove(int index) {
2. RangeCheck(index);//判断是否越界
3.
4. modCount++;
5. E oldValue = (E) elementData[index];
6.
7. int numMoved = size - index - 1;//新的数组长度
8. if (numMoved > 0)
9. System.arraycopy(elementData, index+1, elementData, index,
10. numMoved);
11. elementData[--size] = null; // Let gc do its work
12.
13. return oldValue;//返回删除前的数据
14. }
内部删除方法,跳过越界检查,不返回删除元素的值:ArrayList内部调用的删除方法
Java代码
1. /*
2. * Private remove method that skips bounds checking and does not
3. * return the value removed.
4. */
5. private void fastRemove(int index) {
6. modCount++;
7. int numMoved = size - index - 1;
8. if (numMoved > 0)
9. System.arraycopy(elementData, index+1, elementData, index,
10. numMoved);
11. elementData[--size] = null; // Let gc do its work
12. }
删除指定元素:
Java代码
1. public boolean remove(Object o) {
2. if (o == null) {
3. for (int index = 0; index < size; index++)
4. if (elementData[index] == null) {
5. fastRemove(index);
6. return true;
7. }
8. } else {
9. for (int index = 0; index < size; index++)
10. if (o.equals(elementData[index])) {
11. fastRemove(index);
12. return true;
13. }
14. }
15. return false;
16. }
清空列表:
Java代码
1. public void clear() {
2. modCount++;
3.
4. // Let gc do its work
5. for (int i = 0; i < size; i++)
6. elementData[i] = null;
7.
8. size = 0;//设定元素大小为0
9. }
添加集合c中的元素到ArrayList的末尾,添加成功返回true,如果集合c为空,返回false。
Java代码
1. public boolean addAll(Collection<? extends E> c) {
2. Object[] a = c.toArray();
3. int numNew = a.length;
4. ensureCapacity(size + numNew); // Increments modCount
5. System.arraycopy(a, 0, elementData, size, numNew);//添加到列表的末尾
6. size += numNew;
7. return numNew != 0;
8. }
在指定位置插入集合中的所有元素,和上面一个方法基本差不多,指定位置元素和以后的都要后移
Java代码
1. public boolean addAll(int index, Collection<? extends E> c) {
2. if (index > size || index < 0)//判断越界
3. throw new IndexOutOfBoundsException(
4. "Index: " + index + ", Size: " + size);
5.
6. Object[] a = c.toArray();
7. int numNew = a.length;
8. ensureCapacity(size + numNew); // Increments modCount
9.
10. int numMoved = size - index;
11. if (numMoved > 0)//两种情况
12. System.arraycopy(elementData, index, elementData, index + numNew,
13. numMoved);
14.
15. System.arraycopy(a, 0, elementData, index, numNew);//这里是等于0的情况也就是说直接在数组后面加
16. size += numNew;
17. return numNew != 0;
18. }
删除指定范围的元素
Java代码
1. protected void removeRange(int fromIndex, int toIndex) {
2. modCount++;
3. int numMoved = size - toIndex;
4. System.arraycopy(elementData, toIndex, elementData, fromIndex,
5. numMoved);
6.
7. // Let gc do its work
8. int newSize = size - (toIndex-fromIndex);
9. while (size != newSize)
10. elementData[--size] = null;
11. }