[Java 源码] ArrayList

对于源码的学习, 我是这样的, 简单地过一遍, 看一下有那些方法, 重点看经常问到的源码问题.

ArrayList 的基础属性

private static final int DEFAULT_CAPACITY = 10;

    // 空实例数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 默认大小的空实例数组, 在第一次调用 ensureCapacityInternal 的时候初始化长度为 10
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 存放元素的数组
    transient Object[] elementData; // non-private to simplify nested class access
    // 数组当前的长度
    private int size;
    // 参数为初始长度的构造方法
    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;
    }
    // 参数为 collection 的构造方法
    public ArrayList(Collection<? extends E> 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;
        }
    }

从上面的源码可以看到, ArrayList 的底层实现是动态数组, 有三个不同的构造方法.

 ArrayList 的常用方法 :

get 方法 :

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

        return elementData(index);
    }

根据下标返回元素, 底层是数组, 所以先需要检查索引是否越界, 然后返回索引对应位置的元素

set 方法 :

public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

同样先检查索引是否越界, 然后取出原元素, 将新元素放到该位置

add 方法 : 

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

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

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

先对判断当前 size  + 1 是否需要扩容, 然后进行元素的添加;

如果是在指定位置添加元素, 那么需要将元素位置后面的元素移位, 使用的是 System.arraycopy() 方法

 

remove 方法 :

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);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

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

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        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
    }

remove(int index) : 这里使用了一个 numMoved 变量, 如果要删除的元素为 list 最后的元素, 那么不需要进行移位, 否则就移位, 将最后一位的元素设置为 null.

remove(Object o) : 如果传入的元素为 null, 那么就遍历 list 找到元素为空的地方, 使用 fastRemove 将其删除.

fastRemove (int index) : 和 remove 类似, 删除指定位置的元素.

 

clear() 方法 : 

public void clear() {
        modCount++;

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

        size = 0;
    }

遍历列表将该 list 清空, 所有位置的元素都赋值为 null

 

扩容

这个应该是重点

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);
        }
    }
    // 如果当前数组是给定的默认大小空数组实例, 那么就将其最小容量设置为默认容量, 即 10
    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 void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 新的数组容量为 老容量 + 老容量/2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 这个 if 判断是对初始化情况的兼容
        // 初始容量小于 10 的扩容就给 10 的大小
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 将原数组拷贝到容量是 newCapacity 的新数组中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

总结 :  ArrayList 的底层实现是动态数组,  而在扩容的过程中使用了 Arrays.copyOf() 和 System.arraycopy() 方法来将元素拷贝到新的数组中, 所以我们也需要学习一下这两个方法 :

Arrays.copyof() :

public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

这里的实现调用了另外一个三个参数的 copyOf 方法 :

 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        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;
    }

最后一个参数指定了想要转换的数据的类型, 这里实际上是调用了 System.arraycopy() 方法 :

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

native 说明这是一个调用了 c/c++ 的方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值