7-java安全基础——ArrayList,Vector源码分析

Posted songly_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了7-java安全基础——ArrayList,Vector源码分析相关的知识,希望对你有一定的参考价值。

目录

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

java基础之ArrayList 和VectorCopyOnWriteArrayList

java面试基础必备

使用 std::vector,为啥 &vec[0] 是未定义的行为,但 vec.data() 是安全的?