7-java安全基础——ArrayList,Vector源码分析
Posted songly_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了7-java安全基础——ArrayList,Vector源码分析相关的知识,希望对你有一定的参考价值。
目录
ArrayList集合
当创建ArrayList对象时,如果使用的是无参构造器,那么初始elementData数组的容量为0,第一次添加元素时elementData数组的容量则扩容到10,如果再次扩容则为1.5倍。
通过一个示例程序来分析ArrayList集合底层扩容的机制
public class CollectionTest1 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
//第一次调用add方法时,elementData数组大小为10
for (int i = 0; i <10; i++){
arrayList.add(i);
}
//第二次扩容按1.5倍
arrayList.add(11);
}
}
使用无参构造器来创建ArrayList对象,默认会创建一个空数组,如下所示:
//创建Object[]类型的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList集合的无参构造
public ArrayList() {
//空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayList底层会维护一个Object类型的elementData数组来存储添加的元素,因此可以往ArrayList集合中添加任意类型的元素,其定义如下:
transient Object[] elementData;
每次调用add方法添加元素时都会调用ensureCapacityInternal方法判断elementData数组是否需要扩容,可以看到add方法没有被synchronized关键字修饰,当有多个线程同时添加元素时Arraylist并不能保证数据的完整性,也就是说ArrayList是一个线程不安全的集合。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
然后调用calculateCapacity方法来判断elementData数组是否需要扩容
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity方法首先会判断elementData 数组是否为空数组,如果elementData 为空数组会使用默认方式进行扩容,如果是第一次添加元素的话,会返回DEFAULT_CAPACITY默认容量为10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断是否为空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//扩容的容量
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
调用ensureExplicitCapacity方法会进一步判断elementData数组的大小确定是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
//判断elementData数组容量
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount用于记录集合被修改的次数,然后接着判断elementData数组容量,只有当扩容的容量minCapacity超过elementData数组的大小才会调用grow方法进行真正的扩容,如果扩容的容量minCapacity小于elementData数组的大小说明不需要扩容。
来分析grow方法是如何扩容的
private void grow(int minCapacity) {
// overflow-conscious code
//记录数组大小
int oldCapacity = elementData.length;
//这里是按1.5倍进行扩容
//计算数组需要扩容的容量
int newCapacity = oldCapacity + (oldCapacity >> 1);
//判断是否为第一次扩容
if (newCapacity - minCapacity < 0)
//按默认扩容容量进行扩容
newCapacity = minCapacity;
判断扩容的容量是否超过最大的限制
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//数组拷贝(即扩容)
elementData = Arrays.copyOf(elementData, newCapacity);
}
oldCapacity用于记录elementData数组的大小,(oldCapacity + (oldCapacity >> 1))表示按1.5倍进行扩容(根据elementData数组的大小来计算1.5倍扩容的容量),如果是第一次扩容259行代码计算的结果为0,只有从第二次扩容开始才会以1.5倍进行扩容。因此260行代码的作用是判断elementData数组是否为第一次扩容,如果elementData数组扩容的容量newCapacity小于默认扩容的容量minCapacity则说明是第一次扩容,那么会按默认扩容的容量进行扩容(minCapacity),MAX_ARRAY_SIZE为集合的容量最大值,如果newCapacity比MAX_ARRAY_SIZE的值还大的话,那么hugeCapacity方法会重新计算一个容量的值给newCapacity。
然后调用Arrays类的copyOf方法就会将elementData 数组进行扩容到newCapacity指定的容量,可以看到当执行copyof方法后,elementData 数组大小就变成了10,ArrayList集合底层是用的数组,如下所示
当arrayList对象调用add方法添加元素11时会进行第二次扩容,按1.5倍进行扩容,copyOf方法在进行扩容的同时会把原先elementData数组的内容也拷贝过来,如下所示:
grow方法在进行第二次扩容时,259行代码oldCapacity向右移1位结果为5,oldCapacity + (oldCapacity >> 1)就是相当于10+5,按1.5倍方式进行扩容,因此newCapacity的结果为15。
如果创建ArrayList对象使用的是指定大小的构造器,则elementData数组容量为指定大小,如果需要扩容,elementData数组直接扩容到1.5倍
public ArrayList(int initialCapacity) {
//指定数组容量
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果为0则使用默认方式扩容
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果为负数抛异常
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
总结:
ArrayList集合底层是用的动态数组实现的,元素的存取有序且可重复,效率高但线程不安全。
Vector集合
还是通过一个简单的示例程序来分析Vector集合
public class CollectionTest2 {
public static void main(String[] args) {
Vector vector = new Vector();
for(int i = 0; i < 10; i++) {
vector.add(1);
}
vector.add(11);
}
}
当调用Vector的无参构造创建数组时,无参构造实际上又会调用有参构造并初始化数组的容量为10,参数capacityIncrement用于控制是否以2倍进行扩容,如下所示:
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
//控制是否以2倍进行扩容
this.capacityIncrement = capacityIncrement;
}
public Vector(int initialCapacity) {
//第二个参数控制是否以2倍进行扩容,capacityIncrement=0表示以2倍扩容
this(initialCapacity, 0);
}
//数组初始容量为10
public Vector() {
this(10);
}
vector集合调用add方法添加元素会调用ensureCapacityHelper方法先检查数组是否需要扩容,modCount是用于记录Vector集合修改次数,可以看到add方法时被synchronized关键字修饰,不存在多个线程同时修改数据,这是一个线程安全的函数。
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
ensureCapacityHelper方法中if判断当扩容的容量大于数组大小说明需要进行扩容
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
//是否需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
当vector数组调用add方法添加元素11时会进行第二次扩容,前面我们说过capacityIncrement 参数表示是否以2倍进行扩容,如果capacityIncrement 小于0则按照2倍进行扩容。
private void grow(int minCapacity) {
// overflow-conscious code
//表示根据数组的大小来计算2倍扩容的容量
int oldCapacity = elementData.length;
//按照2倍进行扩容
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
//是否为第一次扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//扩容的容量是否超过最大限制数
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
如果以2倍进行扩容,那么就是newCapacity = oldCapacity + oldCapacity ,那么newCapacity扩容的容量就是20,如下所示
总结:
Vector集合和ArrayList集合一样,底层也是用可变数组实现的,元素是有序的且可重复,效率低但线程安全。如果追求效率使用ArrayList,如果追求安全性使用Vector。
以上是关于7-java安全基础——ArrayList,Vector源码分析的主要内容,如果未能解决你的问题,请参考以下文章
7-java安全基础——ArrayList,Vector源码分析
JavaSE8基础 Collections.synchronizedList 将不安全的ArrayList转为安全
java基础之ArrayList 和VectorCopyOnWriteArrayList