【Java源码】基于数组实现的ArrayList(上)

   

目录

  基本属性:

           构造方法

  给定容量的构造方法

  无参构造方法

  根据已有的Collection构造ArrayList

  

         “修剪数组” 即 去除多余的(多申请的空间)

ensureCapacity确保数组容量

grow扩容

大小,是否为空,是否包含

indexOf 查找某指定成员的第一个下标

lastIndexOf 查找某指定成员的最后一个下标

clone拷贝

toArray 将ArrayList转化为数组   无参方法

toArray 将ArrayList转化为数组   参数:一个数组

get 得到对应下标元素

set 给对应下标赋值

add 增加  (参数为元素)

add 增加  (参数为下标 + 元素)插入到下标元素前

remove 删除 (参数:下标) 删除指定下标的元素 

remove 删除 (参数:元素) 删除指定元素 

clear  清空ArrayList


众所周知,Java中ArrayList是基于数组实现的

    咱们先看其基本属性:

   
    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] EMPTY_ELEMENTDATA = {};

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    transient Object[] elementData; 

    private int size;

  从上到下依次给出了

  大小为10 的初始大小

  为空的Object数组

  默认为空的Object数组

  不可序列化的Object数组

  ArrayList的大小

 

 构造方法

  给定容量的构造方法

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;
    }

  默认构造一个空数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)

  根据已有的Collection构造ArrayList

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;
        }
    }

  首先把collection转化为数组(利用Collection的toArray()方法)

  下面进行了一些判断:

   判断该数组大小是否为0,如果为0则构造一个空数组,不为0则继续下面的判断

               判断该数组的成员类型是否是Object,如果是则拷贝这个数组(利用Arrays的copyOf(目标数组,大小,数组类型));                                                

  方法

   “修剪数组” 即 去除多余的(多申请的空间)

public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

  modCount应该是一个计数器,目前还不知道其作用。

  判断size是否为0,是则直接返回一个空数组,否则以大小为size拷贝数组。

  ensureCapacity确保数组容量

  其实最先看这个的时候并不知道他有什么用,直到看到最后看到 明确容量扩容 的操作的时候,开始怀疑这个应该是在为添加做准备

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);
        }
    }

 参数为minCapacity,即最小容量

 先通过判断数组是否为空,得到minExpand(最小扩大),如果不为空,则默认minExpand = 10;

 minCapacity(最小容量)大于 minExpand(最小扩大),要进行ensureExplicitCapacity,确保明确的容量

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

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

  如果最小容量minCapacity大于数组大小的话,进行

  grow扩容

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //Integer.MAX_VALUE = 0x7fffffff
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);
    }

  这里可以看到grow函数将数组大小扩大到其1.5倍;

  所以由此得出,新数组容量必须  大于等于 原数组大小的1.5倍,  接下来还要进行判断

                如果最小容量minCapacity小于0,  异常       溢出!

  如果大于MAX_ARRAY_SIZE,就返回 Integer.MAX_VALUE

                否则返回 MAX_ARRAY_SIZE

  最后利用copyOf方法将elementData扩容。

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

通过以上一系***保数组容量 的操作,我们可以发现。ArrayList不是无限大的,其最大为 Integer.MAX_VALUE  2147483647

 大小,是否为空,是否包含

public int size() {
        return size;
    }

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

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

 这几个很简单,在这里就不多做赘述了。

  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;
    }

  参数:Object      分了三种情况,为空,能找到和找不到

  lastIndexOf 查找某指定成员的最后一个下标

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

  思路还是同上,不过这里遍历的顺序是从尾部开始(逆序)

  clone拷贝

public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

  这里用到了Array.copyOf方法

  toArray 将ArrayList转化为数组   无参方法

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

  同样用到了Array.copyOf方法

  toArray 将ArrayList转化为数组   参数:一个数组

@SuppressWarnings("unchecked")
    public <T> 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;
    }

 若参数数组长度小于ArrayList长度, 则返回新数组 = ArrayList数组

                                            若大于, 则返回新数组 = 参数数组 (前ArrayList数组长度为ArrayList元素,+ null + 剩下的为参数数组对应下标元素)

目前并不是很清楚toArray有参方法存在的意义,自己认为这个null可能是个“分界点”

  get 得到对应下标元素

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

        return elementData(index);
    }
    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

  返回对应下标元素,首先判断了下标是否越界(rangeCheck(index)),越界则会抛出异常,并输出其Index和size

  奇怪的是,这里并没有判断参数index是否为负,自己试了下,传参为负时还是会报异常,不过没有输出,那为负时这里的异常是如何产生的呢?  这个问题在自己的下一篇博客中得到了解释    》》》传送门《《《

 set 给对应下标赋值

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

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

  还是先进行下标判断(rangeCheck(index)),然后更改下标对应元素的值,并返回旧值

  add 增加  (参数为元素)

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

 add方法最重要的在他的第一步ensureCapacityInternal,确保足够容量可以add进去

 然后进行ensureExplicitCapacity扩容,上面说过这个方法

add 增加  (参数为下标 + 元素)插入到下标元素前

    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++;
    }
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

同样先判断了下标的合法性,再确定了有足够容量可以add

使用System.arraycopy(原数组,原数组开始下标,目标数组,目标数组开始下标,要copy数组的长度)

这步的作用就是将index下标表示的元素及以后统统后移一个“长度”

eg:若index = 2 

before:0 1 2 3 4 5 6 

after:   0 1 2 2 3 4 5 6

剩下的步骤就显而易见了。

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;
    }

  还是先检查下标的合法性,根据下标得到oldValue

  numMoved是计算得到的删除元素后还有多少个元素

  再通过arraycopy将这些元素前移一个单位,--size,达到删除目的

  remove 删除 (参数:元素) 删除指定元素 

    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 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
    }

  遍历数组删除元素,核心在fastRemove方法

  其主要步骤同 remove(int index)

  clear  清空ArrayList

    public void clear() {
        modCount++;

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

        size = 0;
    }

  遍历清空

 

  未完待续。。。0(>_<)0

全部评论

相关推荐

无情咸鱼王的秋招日记之薛定谔的Offer:好拒信,偷了,希望有机会用到
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务