Java源码初学_ArrayList

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java源码初学_ArrayList相关的知识,希望对你有一定的参考价值。

一.ArrayList的构造器和构造方法

  在ArrayList中定义了两个空数组,分别对应当指定默认构造方法时候,指向的数组已经给定容量,但是容量等于0的时候,指向的数组.此外在构造函数中传入Collection对象的时候,直接调用对象的toArray方法,并且将容器内部的引用指向得到的数组.源代码如下:

private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 默认的数组的容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 在默认构造函数中给定的空对象数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
      在有参数的构造函数中,如果参数为0,使用这个空对象数组
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     装元素的数组
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * 容器大小
     *
     * @serial
     */
    private int size;

    /**
     *如果指定容量>0.new一个object数组
      如果==0.使用指定容量为0的引用指向
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

       public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            //toArray方法可能不会返回Object数组
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 将引用指向空数组.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

二.动态调整大小(trimToSize方法)

  该方法使得容器内部的数组的长度和其储存元素的数目相同,通过调用Arrays.copyOf方法(在ArrayList的实现中大量用到了这个方法),完成了将容器内部的引用变量指向一个新的长度为容器中元素的个数的数组.

public void trimToSize() {
        modCount++;//属于对容器的容量进行修改的操作,modCount+1
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

三.ensureCapacity方法

  这个方法增加 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。需要注意的是,当数组引用指向默认构造函数的空数组的时候,将会判断是否需要增加的容量<10,如果小于10,则直接增加数组的长度至10.当数组的长度不足以容纳元素的时候,会直接将数组的长度变为原来的1.5倍.代码如下:

public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            //如果不是默认的构造函数,下限为0
            ? 0
            // 如果是默认的构造函数.那么下限为10
            // 在给定的容量小于10的情况下,不需要扩容
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)//旧容量的1.5倍<mincapacity的时候,才使用mincapacity
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //elementData指向一个copy好的数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

四.indexOf方法

  由于ArrayList方法底层是数组的实现,因此对于AbstractList的indexOf方法提供了复写,底层采用了数组来实现该方法.

public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

五.add方法

  ensureCapacity保证了如果是默认构造函数,那么建立10个容量的数组,不是的话,则数组即将满的时候,扩容数组.(新数组的容量一般情况下都是老数组容量的1.5倍).

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 增加modCount以及容量~
        elementData[size++] = e;
        return true;
    }

六.add(int index,E element)方法

  该方法采用System.arrayCopy方法将index位置及以后的元素全部拷贝到index+1的位置处,并且将数组的长度加1.随后在index索引处赋值.

public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

七.addAll方法.

  采用将Collection集合转化为数组的方式,在调用System.ArrayCopy方法来完成对于数组的增加.

public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // 增加modCount的值
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

八.removeAll和retainAll方法

  removeAll和retainAll方法通过向同一个方法传入不同的参数,实现移除元素,方法直接在数组对象中记录与给定Collection对象相同的或者不同的元素.

public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }
//在此方法中进行removeAll和retainAll方法的实现:
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;//建立一个数组类型的引用指向内存中的数组对象
        int r = 0, w = 0;//r遍历数组的索引.w记录相同的元素或者不同的元素
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;        //w索引之前记录了相同的或者不同的元素
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;

 

以上是关于Java源码初学_ArrayList的主要内容,如果未能解决你的问题,请参考以下文章

Java源码初学_LinkedList

Java源码初学_AbstractList&AbstractCollection

(第一期)大厂面试系列_ArrayList 公众号java源码栈

Java经典小程序_问题源码详解(初学者必备)

Java经典小程序_问题源码详解(初学者必备)

源码阅读Java集合 - ArrayList深度源码解读