深入了解Java ArrayList及其动态调节数组容量机制

来源:互联网 发布:怎么做时间轴java 编辑:程序博客网 时间:2024/06/16 05:03

我们知道,java是允许在运行时才确定数组大小的,Object[] obj = new Object[actualSize];但是这样并没有解决动态更改数组的问题,一旦空间大小被确定,就无法再扩大了,ArrayList很好的解决了这个问题,它可以动态地增加数组列表的容量。

1.构造:ArrayList< Object> obj = new ArrayList< Object>();

jdk1.7构造函数源码:    private transient Object[] elementData;    private int size;    public ArrayList(int initialCapacity) {        super();        if (initialCapacity < 0)            throw new IllegalArgumentException("Illegal Capacity: "+                                               initialCapacity);        this.elementData = new Object[initialCapacity];    }    /**     * Constructs an empty list with an initial capacity of ten.     */    public ArrayList() {        this(10);    }

ArrayList类中存在两个私有成员(当然不仅仅只有两个),elementData是一个Object类型的数组,用来保存元素;size表示ArrayList中实际的元素数目(所以size()方法返回的是实际元素的数目)。我们可以看到以这种形式定义的数组列表的默认容量是10。这里注意一下,在以上的构造函数中,并没有给私有成员size赋值,因为size在这里表示的是实际的元素数目,而刚刚初始化之后,数组列表中是没有任何实际元素的,所以size默认为0。所以,在使用obj.get(0)是会报错的。

    public E get(int index) {        rangeCheck(index);        return elementData(index);    }    private void rangeCheck(int index) {        if (index >= size)            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));    }    E elementData(int index) {        return (E) elementData[index];    }

2.除了以上的构造方法外,当我们大致可以估计需要存储多少元素数量时,还可以用这种方法:ArrayList< Object> obj = new ArrayList< Object>(20); 这样数组列表的容量就是20了,这等价于

ArrayList<Object> obj = new ArrayList<Object>(20);obj.ensureCapacity(20);

我们来看一下ensureCapacity(minCapacity)的源码

    public void ensureCapacity(int minCapacity) {        if (minCapacity > 0)        ensureCapacityInternal(minCapacity);    }    private void ensureCapacityInternal(int minCapacity) {        modCount++;        // overflow-conscious code        if (minCapacity - elementData.length > 0)            grow(minCapacity);    }     private void grow(int minCapacity) {            // overflow-conscious code        int oldCapacity = elementData.length;        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);    }

看了这几段代码,ensureCapacity(minCapacity)的作用就显而易见了,就是增加数组列表的容量,原理很简单,首先判断该参数是否大于0并且是否大于当前容量,然后就到了grow方法,其他的语句先别管,看最后一句,elementData = Arrays.copyOf(elementData, newCapacity); 这条语句先按指定参数创建一个更大的数组,然后把原数组的元素一个个拷贝到大数组中,最后将elementData 的引用指向这个大数组。这样,就增大了数组列表的容量,而小数组也会被gc回收。所以数据量很大的时候还是建议初始化的时候就指定容量,提高效率

Arrays: 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;}

3.看到这里,也对ArrayList动态增加数组容量有了一定的理解,现在来看看add方法在添加新元素时是怎么动态增加数组容量的

     public boolean add(E e) {        ensureCapacityInternal(size + 1);  // Increments modCount!!        elementData[size++] = e;        return true;    }    private void ensureCapacityInternal(int minCapacity) {        modCount++;        // overflow-conscious code        if (minCapacity - elementData.length > 0)            grow(minCapacity);    }     private void grow(int minCapacity) {            // overflow-conscious code        int oldCapacity = elementData.length;        int newCapacity = oldCapacity + (oldCapacity >> 1); //jdk1.7之后的容量增长机制是这样的,还有这样计算的newCapacity = (oldCapacity * 3)/2 + 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);    }

其实原理一样,因为数组的大小一旦确定是不能被改变的,所以当内部数组已经满了之后还要再添加元素时,是创建一个更大的数组,然后把原数组的元素一个个拷贝到大数组中,最后改变引用指向。这里有个疑问,add方法动态增加了多少容量呢,现在可以看一下grow方法的其他语句, int newCapacity = oldCapacity + (oldCapacity >> 1); 假设原容量为10,那么新容量就为15,再次增加容量会变成22,原容量不同,每次增加的容量也就不同。jdk1.7之后容量增加的计算机制是这样的,我还见过有这样的:newCapacity = (oldCapacity * 3)/2 + 1。这里要注意,如果原容量为10,使用ensureCapacity(11),得到的新的容量不是11,而是15。所以,ensureCapacity并不是按照参数来指定新容量的。

4.

ArrayList<Object> array = new ArrayList<>();Object[] obj = new Object[10];

这两条语句都开辟了10个元素的空间,一个表示数组列表,一个表示数组。虽然ArrayList是用数组来实现的,这两者还是有一定区别的,数组有大小,是固定的;数组列表拥有的是容量,并且是可变的,数组和数组列表在初始化之后,System.out.println(obj[0]); 可以打印null;而System.out.println(array.get(0));会报错。如果不看源码,还以为数组列表连存储空间都没开辟。

5.看源码可以知道ArrayList里的方法都不是同步的,所以当不止一个线程要修改ArrayList实例时,必须让它保持外部同步。可以使用List list = Collections.synchronizedList(new ArrayList(…)); 来防止这一问题发生。

原创粉丝点击