集合的实现原理

首先明白一点,Java中集合的根本就是数组和链表。

 集合分为ArrayList 和LinkedList。

ArrayList中封装的是数组,LinkedList中封装的是链表。

数组与链表的区别:

  1、数组中的内存地址连续,所以它的读取速度很快。缺点就是容量固定。

  2、链表的写入速度很快,但是读取速度不如数组。

  • ArrayList的实现原理

我们先来new一个ArrayList

    List<String> list = new ArrayList<String>();

    //ArrayList.java源码中的处理

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    transient Object[] elementData; //成员变量Object数组
    
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /*
     *当我们new一个ArrayList时,就是赋了一个空的数组。
     */

在ArrayList.java中有一个成员变量 elementData,这里可以看出ArrayList的实现就是一个Object数组。

而我们平成用的add方法,实际就是向数组中添加元素。

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;  //这里向elementData中添加元素e
        return true;
    }

现在还有一个问题,那就是Object这个数组的长度是如何确定的呢?我们在前面也说了,数组是有固定容量的,那这里的数组是如何管理这个容量呢?

    private static final int DEFAULT_CAPACITY = 10;
    /*首先我们看到有一个 capacity 默认是10 */

这里的capacity何时生效呢,肯定是在添加元素的时候,我们再回到上面的add(e)方法,发现了一个叫ensureCapacityInternal(size)的方法,那就看看他干了些什么。

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    /*我们先关注一下这里的变量 minCapacity
     *minCapacity是在add方法中传入的,是size+1,第一次添加元素的时候size=0
     *所以此时的minCapacity = 1,elementData在第一次的时候自然就是{}空的
     */
    //来看一看calculateCapacity方法

    private static int calculateCapacity(Object[] elementData, int minCapacity) { //{} ,1
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    /* 分析这里的elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 
     * 我们知道在我们new ArrayList() 是 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * 所以在第一次添加元素是这里的条件为true。
     * 再来看这一句 Math.max(DEFAULT_CAPACITY, minCapacity)
     * 将它转换一下 Math.max(10, 1)
     * 这样返回的就是10。
     */
     //再看一下ensureExplicitCapacity方法
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    //modCount 是一个成员变量,主要用来记录List对象被修改的次数,在这里我们暂时不用关注。
    //minCapacity - elementData.length > 0 通过这里我们可以看到这个方法主要是判断是否要扩容。

从ensureExplicitCapacity方法中可以看出当最小容量 minCapacity 大于Object数组的容量时数组就会进行扩容。

    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);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    /* 这个方法中这行代码 oldCapacity + (oldCapacity >> 1) ,这句的意思就是说
     * 新的容量等于老容量的二分之三,oldCapacity >> 1 这句话就是取老容量的一半。
     * 着这里还发现一句话 newCapacity - MAX_ARRAY_SIZE > 0 ,这句话是当数组长度超过
     * 数组最大值怎么办,那我们首先来看一下数组的最大值MAX_ARRAY_SIZE这个静态常量。
     */
    
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    //这里可能会有些疑问,为什么要取int最大值-8,这个8是从哪来的呢?
    //在上面的注释中有这样一句话
    //Some VMs reserve some header words in an array.
    //大致的意思就是vm会保留一些对象的头信息,头信息的最大占用内存不会超过8字节
    
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
    }
    // 注:minCapacity是数组当下实际的容量。
    /* 再来看这里 (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE
     * 这也是我不太明白的地方,当最小容量大于静态常量是,返回int的最大值。
     * oldCapacity<=minCapacity<=newCapacity<=Integer.MAX_VALUE 这是容量应该满足的原则。
     * 我们要让 newCapacity 尽可能的满足 minCapacity 。如果不能满足,一定要抛异常 
     */ 
    //当完成这些后还有最后的一步,数组的拷贝
    elementData = Arrays.copyOf(elementData, newCapacity);
    //这也是这个过程中最耗费性能的一步,这就是ArrayList写入慢的原因。

下面我们来整理一下集合创建的流程:

1、new ArrayList();
    Object[] = {} 
2、add(element);
    <1> ensureCapacityInternal :确定数组容量
            calculateCapacity :return Math.max(DEFAULT_CAPACITY, minCapacity);
            ensureExplicitCapacity :判断是否要扩容
    <2> elementData[size++] = e; :向数组中添加元素。

在这里有一个问题需要提一下,ArrayList的极限容量不会超过2147483647,假如我这里有一个1000000容量的集合,但是我的数据长度

是1000001,那么他就会进行扩容,扩容后的容量就是1500000,用1500000的容量来存储1000001数据有点奢侈,那ArrayList是如何

解决的呢?

    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }
    //从方法名就可以看出削减规模,他的是实现就是当element.length小于元素的个数并且在含有元素的时候
    //element就会等于新复制的一个数组。

采用这种方法手动的节约了内存空间。但是要注意 trimToSize方法是ArrayList的方法,使用方法如下:

        ArrayList<Integer> list = new ArrayList<Integer>();
        for(int i=0;i<13;i++){
            list.add(i);
        }
        list.trimToSize();
        //要使用ArrayList.trimToSize 。

猜你喜欢

转载自blog.csdn.net/Reganzhang/article/details/82886246
今日推荐