转载自五月的仓颉博客地址:http://www.cnblogs.com/xrq730/p/4989451.html
对于集合需要关注四点
1. 是否允许为空
2. 是否允许重复
3. 是否有序(取出元素的顺序是否和插入的顺序一致)
4. 是否是线程安全的
ArrayList
Arraylist就是一个以数组实现的集合,它的成员变量构成:
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData;
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
elementData: 就是ArrayList集合底层的数组
size:ArrayList中元素的个数,size按照调用add,remove的次数自增或者自减,就是add null也会去自增1
ArrayList的特性
关注点 | 结论 |
ArrayList是否允许为空 | 允许 |
ArrayList是否允许重复 | 允许 |
ArrayList是否有序 | 有序 |
是否线程安全 | 线程不安全 |
构造函数
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
this(10);
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
实例化集合的时候,可以指定集合的容量(就是底层数组的容量),不指定容量大小默认size=10
也能传入一个集合作为初始化参数,此时,会将集合转为数组,并通过Arrays.copyOf生成新的数组,将引用指向这个新的数组
添加元素和扩容
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
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);
}
}
添加元素:就是在数组的某个位置记录下元素的地址,新加入的元素时放在后面
扩容: 从源码可以看出,每次添加元素前会调用ensureCapacity进行一次扩容检查,如果当前元素个数大于ArrayList的容量大小就会进行一次扩容,这也就是说ArrayList的底层是基于动态数组实现的原因,具体来说:将新的数组的容量扩容为之前的1.5倍的加1(jdk1.7中改为1.5倍扩容),在调用Arrays.copyOf将原来数组的元素,复制到新的数组,并将elementData引用指向它. 这个过程中Arrays.copyOf创建新的数组,并复制元素,这就是为什么说扩容很很费资源的原因.
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
可能有人会问,为什么要这样扩容
1、如果一次性扩容扩得太大,必然造成内存空间的浪费
2、如果一次性扩容扩得不够,那么下一次扩容的操作必然比较快地会到来,这会降低程序运行效率,要知道扩容还是比价耗费性能的一个操作
所以扩容扩多少,是JDK开发人员在时间、空间上做的一个权衡,提供出来的一个比较合理的数值。
删除元素
1. 按照元素的下标删除
public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) 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;
}
2. 按照元素删除,删除首次出现的这个元素
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;
}
对于ArrayList来说,两者几乎差不多,都是调用下面一段代码
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
1. 将index+1开始的元素,向前移动一个位置
2. 将最后一个元素指定为null,可以让gc去回收它
插入元素
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
和直接add元素类似,也会先调用一次扩容方法,然后,将index开始的元素整体向后移动一位,最后在index位置存放元素地址
ArrayList fail-fast策略
ArrayList也采用了快速失败的策略,通过记录modCount(每次去修改ArrayList结构都会去修改这个值)来实现。在并发的情况下,使用迭代器迭代,会检查modCount和expectedModCount是否相等,如果不相等,抛出异常。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ArrayList的优缺点
1. ArrayList底层是数组实现的,是一种随机访问模式,加上它实现了RandomAccess接口,因而查询元素很快
2. ArrayList在顺序添加一个元素,并且添加元素的个数不大于ArrayList的容量大小,这个操作只是会将元素的位置存放在数组的索引位置,不会耗费什么资源,会很快。(也就是如果能够事先指定ArrayList的容量大小,顺序添加元素很快)
3. ArrayList在删除元素和插入元素的表现较差,如果元素很多,移动元素会很耗费资源
结论: ArrayList适合查询元素和在指定容量的前提下,顺序添加元素
ArrayList和Vector的区别
从ArrayList操作元素的方法可以看出,不是同步的,在多线程环境下,一定会出现线程安全问题,如果想要使用ArrayList又想要他线程安全怎么办?
一个方法是使用Collections.synchronizedList(List) 生成一个线程安全的list
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
Vector 是ArrayList线程安全的版本,它的实现90%和ArrayList一样,区别
1. 它是线程安全的,方法是同步的
2. 构造时候能够指定增长因子capacityIncrement,如果不指定,扩容按照原来的两倍扩容
private void ensureCapacityHelper(int minCapacity) {
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object[] oldData = elementData;
int newCapacity = (capacityIncrement > 0) ?
(oldCapacity + capacityIncrement) : (oldCapacity * 2);
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
另外,jdk1.7中提供一个ensureCapacity公开的方法,手动扩容,这个方法在添加大数据量前调用,能够提高效率。