ArrayList
一、基础
1.父类
Collection->AbstractCollection->AbstractList
2.定义
一个有序的装载对象的动态集合。
2.特点
(1)底层:数组。
(2)默认容量:如果 new ArrayList(),则默认容量为0,在第一次添加数据时,会设置默认容量为10,后续的扩容均进行1.5倍的扩容。
最大值为Integer.MAXSIZE-8:214 748 3647-8,(其中这个8存储的是数组对象的元数据)。
(3)优点:有序的;随机访问(通过下标访问)效率高,适合读取数据;
可添加null以及重复元素;
可自动扩容,每次扩容1.5倍。
(4)缺点:线程不安全,插入和删除速度慢。
(5)写入大量数据时的操作: 如果增加的数据量很大,应该使用list.ensureCapacity()方法,预先设置ArrayList的大小;实例
二、ArrayList的Add()方法解读
Arraylist在进行add操作时:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;//把新对象放到新的位置上
return true;
}
/**
*扩容操作
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
*计算容量
**/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//若当前数组为空数组,则比较10和minCapacity谁大。
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//若不为空数组,否则返回minCapacity
return minCapacity;
}
/**
*判断是否需要扩容
**/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果超出当前容量,则需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
*扩容的增长方法
**/
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//MAX_ARRAY_SIZE指的是Integer.MAX_VALUE - 8;这里要判断是否大于Integer的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//把旧数组拷贝到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 超出MAX_ARRAY_SIZE时,则返回Integer.MAX_VALUE
**/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
(1)定义一个数值 newSize=size+1;【size+1:arrayList的元素个数+1】
根据newSize这个数值进行如下的计算:
若当前数组为空数组,则比较 10 和newSize谁大,谁大则返回谁的数值;
若不为空数组则直接返回newSize 。
返回的这个数值记为minCapacity。
------------------------------------------------------------------------------------------------------------
(2)再确认是否增长:如果 minCapacity 大于 当前的ArrayList的容量 ,则调用增长方法;否则不增长。
------------------------------------------------------------------------------------------------------------
(3)调用增长方法,此时定义一个新数组容量:newCapacity=1.5(size+1);
再定义一个旧的数组容量:oldCapacity=当前ArrayList的容量;
增长方法如下:
第一步:判断 如果 newCapacity 小于 size+1,则增长到 size+1;(这种情况只发生在arraylist容量为空的情况下)
第二步:再判断 如果 newCapacity 大于 int的最大值-8(214 748 3647 - 8),则拿着size+1的这个数值,调用大容量接口得到结果H,把H赋值给newCapacity。
【大容量接口里的具体运算:如果size+1大于 (214 748 3647 - 8 )这个值,则返回int的最大值;否则返回(214 748 3647 - 8 )。】
第三步:调用Arrays的copyOf方法,把原来的数组复制到新的数组里,容量为newCapacity。
------------------------------------------------------------------------------------------------------------
(4)把新元素添加进新数组中,这里是线程不安全的。
【 elementData[size++]=e; 这里CPU是分为两步来执行的:1.elementData[size]=e; 2.size++; 】
三、实现线程安全的方式
Vector确实是一个线程安全的List,但它实现安全的方式是对所有操作都加上了Synchronized关键字,这种方式很影响效率,所以并不推荐使用。
若要实现安全,可以采用Collections.synchronizedList(new ArrayList() )创建或创建一个CopyOnWriteArrayList。
实现线程安全的方法对比(【Collections.synchronizedList( new ArrayList() ) 】方法和【创建CopyOnWriteArrayList】的方法):
Collections.synchronizedList( xx ):适合写数据,读数据比较差。SynchronizedList里只有listIterator()方法没有加锁,其他的方法都是在代码块上加synchronized。
[-插播一下 - Collections.synchronizedList()的源码解析:如果这个list支持随机访问,那么我们就创建一个SynchronizedRandomAccessList,否则我们创建SynchronizedList。]
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
CopyOnWriteArrayList:适合读数据,写数据操作性能差。所有涉及到对数据有变动的操作,都在方法里加ReetrantLock。
[- 插播一下 - CopyOnWriteArrayList的 add() 方法源码解析:加上ReetrantLock后将其变成数组,然后分为两步:
(1)把旧数组中的数据通过Arrays.copyOf()方法放到新数组中(新数组的容量为旧数组容量+1);
(2)把新数据放入新数组中。]
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
四、ArrayList源码中的两个空数组
DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA的区别:
从构造函数上来讲,无参构造时,用DEFAULTCAPACITY_EMPTY_ELEMENTDATA作为引用;有参构造时(如果给定的容量为0或者是个空集合),用EMPTY_ELEMENTDATA作为引用。
若容量为0,则指向EMPTY_ELEMENTDATA。