ArrayList随机访问,动态扩容

随机访问:

ArrayList是一个动态数组,实现了RandomAccess接口,支持随机访问,通过get(i)即可获得相应内存中存放的值。原因是因为ArrayList存放的内容在内存中是连续的,数组直接用[ ]访问,相当于直接操作内存地址,所以随机访问的效率较高。

普通的for循环是随机访问的,所以遍历ArrayList使用普通for循环比增强for循环和迭代器的效率高。

而LinkedList是一个双向链表,链表只能顺序访问,LinkedList中的get方法是按照顺序从列表的一端开始检查,直到找到要找的地址。所以遍历LinkedList使用增强for循环和迭代器的效率高,使用普通for循环会每次都从头开始遍历,效率较差。

动态扩容:

ArrayList是动态扩容的,创建ArrayList时,ArrayList有三个构造函数:

public ArrayList();

默认的构造器,将会以默认的大小来初始化内部的数组 。

public ArrayList(Collection<? extends E> c)

用一个ICollection对象来构造,并将该集合的元素添加到ArrayList。

public ArrayList(int initialCapacity)

用指定的大小来初始化内部的数组。

对于默认的构造方法,在没指定initialCapacity时就是会使用延迟分配对象数组空间,开始时不分配空间,当第一次插入元素时才分配10(默认)个对象空间,当10个空间存储完了,插入第11个时,空间会通过位运算扩容到原来的1.5倍,即变为15个空间。再插入第16个元素时,又会扩容到原来的1.5倍,以此扩容。

扩容是通过grow()方法实现的,源码:

/*
    *增加容量,以确保它至少能容纳
    *由最小容量参数指定的元素数。
    * @param mincapacity所需的最小容量
    */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //>>位运算,右移动一位。 整体相当于newCapacity =oldCapacity + 0.5 * oldCapacity  
        // jdk1.7采用位运算比以前的计算方式更快
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
       //jdk1.7这里增加了对元素个数的最大个数判断,jdk1.7以前是没有最大值判断的,MAX_ARRAY_SIZE 为int最大值减去8(不清楚为什么用这个值做比较)
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 最重要的复制元素方法
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
可以看到,最后求得扩容后新的空间,是通过Arrays.copyOf()方法,copyOf()方法的源码:
public static int[] copyOf(int[] original, int newLength) {  
        int[] copy = new int[newLength];  
        System.arraycopy(original, 0, copy, 0,  
                         Math.min(original.length, newLength));  
        return copy;  
    }  

可以看到,在方法内部新创建了一个数组,然后将原来数组的内容复制进去,返回新数组的引用。新扩容的空间中存放的为数组初始化的默认值,当通过add()方法向ArrayList中加入元素时,会覆盖掉原来的默认值。

这样的扩容方法会浪费一定的空间,可以调用TrimSize方法,这个方法用于将ArrayList固定到实际元素的大小,当动态数组元素确定不在添加的时候,可以调用这个方法来释放空余的内存。

扩容的时间是在add()方法空间不够时,add方法的源码:

public boolean add(E e) {
        //确保内部容量(通过判断,如果够则不进行操作;容量不够就扩容来确保内部容量)
        ensureCapacityInternal(size + 1);  // ①Increments modCount!!
        elementData[size++] = e;//②
        return true;
    }

当add()容量够时,就是直接在后面添加,速度很快。当add()容量不够时,就将新建一个更大的数组,然后把旧数组的内容复制过去。当在中间位置插入时,会把插入点及后面的数据后移一个位置。然后插入。当在中间位置删除时,会将删除点后面的数据前移一个位置。所以说任何时间点,其内存都是连续的,随机索引访问效率很高。插入,删除效率低。或者容量满时add()效率低。




猜你喜欢

转载自blog.csdn.net/wanderlustlee/article/details/80744277