ArrayList源码分析

在解析源码前,我们先要认识两个重要的数组函数。它们分别是
①、public static native void System.arraycopy(Object srcArray,int srcPos,Object destArray,int destPos,int length);

②、public static   T[]  copyOf(U[] original, int newLength, Class newType);

1、首先说一下第一个函数

它的参数列表如下所示:

参数 srcArray srcPos destArray desPos length
含义 原数组 原数组开始位置 目标数组 目标数组开始位置 拷贝长度
一句话来说,就是将srcArray从srcPos(包括srcPos)开始的length个元素拷贝到destArray以desPos开始的length个元素上。

2、接下来是第二个函数:
参数 original newLength 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;
}
第一行代码 T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[])Array.newInstance(newType.getComponentType(), newLength);
先是判断目标类型是否是Object[],如果是就直接实例化一个newLength的Object数组,如果这个数组已经有确切类型指定比如String,那么就用newType.getComponentType()这个方法得出数组中的元素类型是java.lang.String,然后再用Array.newInstance(newType.getComponentType, newLength)实例化出一个元素类型为String的数组。当然,源码中用的都是泛型指代,这里为了便于理解直接用String讲解

第二行代码 System.arraycopy(original, 0 , copy, 0 ,Math.min(original.length, newLength));
则是赋予copy数组具体内容。
他将original这个原数组从0开始的Math.min(original.length,newLength)个元素拷贝到了copy数组从0开始的n个元素中。完成拷贝工作。
至于为什么Math.min(original.length,newLength)呢,我们可以这么考虑,当newLength>orginal.length时,因为新数组内容要从原数组中拷贝得到,(newLength-original)多出的数组内容就浪费了空间,而当original.length>newLength时,为了保证新数组不会发生OOM,我们自然也要用最小的数了。

下面就进入正题了。我们接下来会先从ArrayList的常用变量讲起,再从它的构造方法下手,然后谈谈它的常用API,增删改查等等。

1、常用变量

//序列化的版本号
private static final long serialVersionUID = 8683452581122892189L;

//默认的数组大小为10
private static final int DEFAULT_CAPACITY = 10;

//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};

//默认构造函数中容量为10的数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//真实存放内容的数组
transient Object[] elementData;

//数组大小
private int size;

很明显我们能得到以下信息:ArrayList内部采用的存储结构是数组,默认容量大小为10,用size记录长度,而elementData就是实际存放元素的数组。这里的空数组和容量为10的数组是为了快速方便的构造而使用的,下面的构造函数就会谈到这点。

2、构造方法

    /**默认构造方法,只是初始化容量为10
     *构造方法一
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

	/**自定义初始容量
     *构造方法二
     */
	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);
        }
    }

    /**将实现Collection接口的类对象作为参数初始化数组和size
     *构造方法三
     */
    public ArrayList(Collection c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

 构造方法一是默认的构造方法,这里我们就直接返回DEFAULTCAPACITY_EMPTY_ELEMENTDATA,也就是大小为10的数组

构造方法二我们实例化指定容量的数组,这也没啥好说的。

构造方法三我们把实现了Collection接口的存储着元素的类型和当前实例化出的ArrayList存储的元素类型相同的对象作为参数,我们将它转换为数组类型并用Arrays.copyof()开辟出合适的空间大小的新数组并让elementData指向它。这里有一行注释很有意思,我们来看看:c.toArray might (incorrectly) not return Object[] (see 6260652)。

这里的报错原因是这样的:c.toArray()得到的可能并不是Object[]对象,比如在Arrays中定义的ArrayList中定义的toArray(),它返回的数组就是泛型数组,而不是Object[],因此有下面的实例

Integer[] a = {1,2};
List list = Arrays.asList(a);
System.out.println("转换为List后的类型:"+list.getClass());
Object[] array = list.toArray();
System.out.println("转换回数组后的类型:"+array.getClass());

 

我们可以看到,即使将它转换为Object[],而实际上它的类型确是Integer[]。这就是上述注释的错误,也是要判断elementData.getClass() != Object[].class的原因

3、CRUD接口

3.1  查

我们在ArrayList中查找效率非常高,就是O(1)的复杂度。

E elementData(int index) {
    return (E) elementData[index];
}

public E get(int index) {
    rangeCheck(index);//越界检查
    return elementData(index); //下标取数据
}

我们在用get()查找时要先做一个越界检查,然后就用数组的下标去查询元素了。

3.2 改

我们对ArrayList中元素的修改并不会导致modCount的修改,modCount只会在改变长度的情况下修改。

public E set(int index, E element) {
    rangeCheck(index);//越界检查
    E oldValue = elementData(index); //取出元素 
    elementData[index] = element;//覆盖元素
    return oldValue;//返回元素
}

同样地,我们是使用数组下标查找并修改的元素,因此修改效率也很高。

3.3 增

ArrayList中添加元素时需要先用ensureCapacityInternal()和ensureExplicitCapacity()来确认容量是否充足,如果充足就不需要扩容了,直接添加元素即可。如果不足的话就要用grow()方法扩容了。扩容时默认扩容1.5倍数,如果还不够的话就把最小需要容量作为容量。最后再判断容量是否大于了最大容量MAX_ARRAY_SIZE,如果是就执行hugeCapacity(),判断最小需要容量是否大于MAX_ARRAY_SIZE,如果是那么新容量为Integer.MAX_VALUE,否则就为Integer.MAX_VALUE-8。

要注意的是,扩容操作需要调用Arrays.copyof()把原数组整个复制到新的数组中,操作代价很高。所以如果在实际开发中频繁更改的场景下不宜使用ArrayList。

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : 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++;//如果确定要扩容,就修改modCount

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//此处是判断是否扩容关键
            grow(minCapacity);
    }

	//需要扩容的话默认扩容1.5倍,如果还不够,就用目标的size作为扩容的容量。在扩容成功后会修改modCount
    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);
    }

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

    public void add(int index, E element) {
        rangeCheckForAdd(index);//越界判断

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);//将index开始的数据后移一位
        elementData[index] = element;
        size++;
    }

    public boolean addAll(Collection c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);//复制数组完成复制
        size += numNew;
        return numNew != 0;
    }

    public boolean addAll(int index, Collection c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);//移动(复制)数组

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

 3.4 删

删除操作我们没有再去做扩容,所以用不到Arrays.copyof(),相对的经常调用的函数是System.copy()。

删除某一指定位置的元素就是先判断有无越界,再读出删除的值,再调用System.copy()将要删除的元素后面的元素全都前移一位,再将最后一位设为null,给size-1,由GC自动回收空间,最后再返回删除值。

其他删除操作原理类似。

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);//读出要删除的值

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);//将index+1后numMoved位的元素复制到index后numMoved位            上
        elementData[--size] = null; // clear to let GC do its work
		//把尾数据置空,让GC负责回收
        return oldValue;
    }

    //根据下标从数组取值 并强转
    E elementData(int index) {
        return (E) elementData[index];
    }

    public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);//根据index删除元素
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
        }
        return false;
    }


    //已经判断了越界情况,也不需要取出该元素。
    private void fastRemove(int index) {
        modCount++;//修改modCount
        int numMoved = size - index - 1;//计算要移动的元素数量
        if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);//以复制覆盖元素 完成删除
        elementData[--size] = null; // clear to let GC do its work  //置空 不再强引用
    }

    public boolean removeAll(Collection c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

	//批量删除
    private boolean batchRemove(Collection c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        //将elementData与Collection交集的元素用后面的元素覆盖替代,而w表示执行这部操作后原来的末尾位置,所以下一步操作直接置空w后的所有元素
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            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;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

 

在此之外,我们在介绍一下其它的API。

4、另外的API

4.1  判空

判空
public boolean isEmpty() {
    return size == 0;
}

如上代码所示,判空的复杂度只在O(1)

4.2  包含

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

//用for循环查找值,但要分类o是否为null
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;
}

 复杂度是线性的

4.3 清空

public void clear() {
    modCount++;
    // clear to let GC do its work
    for (int i = 0; i < size; i++)  
        elementData[i] = null;

    size = 0; 
}

 遍历列表并将所有元素置空,size设为0,然后由GC自动回收空间。

4.4  两个toArray()方法

   
    /**
     * 直接copy出Object数组
     */
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }


    /**
     * 运行态时通过传入一个与实例对象存储类型相同的元素,返回一个类型确定的数组
     * 如果传入的数组大小不够就直接copy一个新的数组
     * 如果大于elementData就将elementData上的内容拷贝到传入的数组上,并将其余元素置空
     */
    public  T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

两种方式不同:1、public Object[] toArray();方法直接返回Object[],无需传参,使用简单  2、public T[] toArray(T[] a);运行时返回指定元素类型的数组,很实用

 

你可能感兴趣的:(java,源码,java容器)