Java源码阅读笔记(1)-ArrayList

ArrayList是我们经常见到的一个集合数据类型,所以我们来看看ArrayList是怎么实现的?

   /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    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);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    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;
        }
    }

    /**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */

这是三个构造函数,第一个构造函数是一个参数传入,这个参数的名字已经很详细了,叫做初始容量(initialCapacity),当初始化容量大于0的时候我们就开辟InitialCapacity个内存空间出来,假如说赋值为0,我们就动用EMPTY_ELEMENTDATA(空元素数据),那么这个EMPTY_ELEMENTDATA又是什么呢?我们继续往上翻,因为这些常量的定义往往都放在开头。

 /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

看到这其实已经很明白了,注释的意思也说得很清楚,那就是新建一个空的实例,啥也没有。

 else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }

其他的呢,也就说的很明白了,你有0个capacity也好,10000个capacity也好,但是你传给我-1个什么意思?你在为难这个数据结构是吧?直接抛出异常,非法的初始值。

众所周知,没有参数的我们称其为无参构造(NoArgsConstructor),所以我们仔细看一下ArrayList也是有无参构造的,我们可以发现,ArrayList在实现无参构造的时候会初始化赋个值DEFAULTCAPACITY_EMPTY_ELEMENTDATA

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     * 默认尺寸为空的实例,我们与EMPTY_ELEMENTDATA区分去了解当第一个元素填入膨胀了多少
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

那么我们还注意到第三个构造函数就是collection类型的他也能传,首先将这个传入的c转换成了Collection类型的array,接着用反射判断是不是与Object[]这个类型一致,一致就对这里面的array进行复制操作,不一致就传入一个空的默认数据集合。

接下来就是contain和size了,判断这个arraylist为不为空和这个arraylist的大小的函数
那么有的人会问,这个size和capacity两个参数之间到底有什么区别,其实举个例子来讲,一个杯子500ML,我分5个刻度线,当水注进去的时候我就加个刻度线的标记,这样就可以倒满这500ML(capacity),那么,我倒进去的这个水位就是size,这个集合操作也是一样,我开辟这个空间就是capacity,当我每插入一个数进去size更新进行标记,当我删除的时候也进行size更新,这里的size就相当于个标记,事实上,Java中弱化了对指针的概念,但是size可以理解成指针指向某个空间,不断地在移动。

    /**
     * Returns the number of elements in this list.
     *
     * @return the number of elements in this list
     */
    public int size() {
        return size;
    }

    /**
     * Returns <tt>true</tt> if this list contains no elements.
     *
     * @return <tt>true</tt> if this list contains no elements
     */
    public boolean isEmpty() {
        return size == 0;
    }

那么,问题来了,当我们新建一个ArrayList的时候,我们习惯于传入一个capacity,也就是说,这个List初始化就是一个0的list,我们插进去不就异常了吗?为什么仍然能够正常执行?
首先看一下Add是如何实现的。

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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++;
    }

这里有俩Add,从传入参数大致可以猜出一个是尾部插入,size+1,一个是指定index插入,index后的元素往后挪一位,插入空缺的地方

1 2 3 4 5 6 7 8 9
无index参数 1 2 3 4 5 6 7 8 9 10
index为 2
element为10
1 2 坑 3 4 5 6 7 8 9
1 2 10 3 4 5 6 7 8 9 

那么我回答了这么多,的确还没解决0capacity这个问题。
仔细看下ensureCapacityInternal这个实现。

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

        ensureExplicitCapacity(minCapacity);
    }

假如我们为空的数据集,啥也没有,我们就给他开辟出来,这个minCapacity就从我默认设置的这个capacity或者你设置的minCapacity中选取个较大值,也就是说,你只要操作Add这个方法,甭管你这capacity初始化的时候传没传给我,你这最少我给你开辟10个内存空间。
注意是在add的情况下且数组为空数组开辟的这10个内存空间,并非一些资料上讲的初始化就开辟。
我们继续追踪一下代码 ensureExplicitCapacity(minCapacity)这个方法


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

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

当我们传入mincapacity(10或者你传的参数)小于数据集合的长度,这时,我们的常识就是溢出,满了,塞不下了,当时Java给出了个方案,那就是grow,增长。、

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }

我们看下增长的算法

旧容量就是这个数据集合长度,新容量=旧容量+旧容量>>1(右移一位)也就是扩容1.5倍,
假如计算的数字比我传入的mincapacity小,我就用我传的capacity,
否则就用这1.5倍扩容的新值,再将数据集合赋给扩容后的数组中

说完了增加操作,俗话说应用开发业务逻辑的主要四个方面,增删改查,作为一个CRUD工程师,我们要有良好的职业素养,下一个讨论下删除操作。

    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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;
    }

首先第一个我们要面对的就是rangeCheck这个函数,顾名思义,就是核查范围。假如你传入的参数大于这个size,当然是越界,所以我们要抛出异常,越界异常,终止程序。

    /**
     * Checks if the given index is in range.  If not, throws an appropriate
     * runtime exception.  This method does *not* check if the index is
     * negative: It is always used immediately prior to an array access,
     * which throws an ArrayIndexOutOfBoundsException if index is negative.
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

然后我们就是查找到这个元素所在的位置,当该元素处在index位置时,index+1往index位置移动,整体向前赋值一位,最后那个值毫无意义,我们做size指针前移,指向的空间赋值为空释放掉。

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

我们还看到一个remove传入对象的方法,就是简单的遍历一遍,假如里面有这个对象,就删除,没有就不删,重要的点在于这个fastRemove的实现,既然我们实现了remove,那么这个fastRemove又是怎么来的呢?

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

fastRemove和remove的差别在于fastRemove没有验证index这个选项,而且它是个私有的方法,外界不可调用,所以我们会比较陌生,正因为没有验证,它的速度和执行效率可能会比remove快上一丢丢。

迭代器方面
//有无下一个元素,在于焦点是否等于size
        public boolean hasNext() {
            return cursor != size;
        }
        //next函数是将cursor这个指针往后移
       @SuppressWarnings("unchecked")
       public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
     public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

啥是迭代器?

迭代器(iterator)有时又称游标(cursor)是程序设计的软件设计模式,可在容器(container,例如链表或阵列)上遍访的接口,设计人员无需关心容器的内容。
arrayList中的迭代器lastRet初始化为-1,这就说明为什么上来直接remove会报错,因为这个lastRet的值就是不合法的啊!
当我第一次执行next的时候,我下标就开始变动,lastReturn就会被cursor赋值,返回当前所指向的元素,反复执行,我们就可以做一个清空当前list的操作,也就是说,当hasNext为true的时候,表示一直有下一个元素,指向next,执行remove。

    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        Iterator itr = arrayList.iterator();
        while (itr.hasNext()) {
            itr.next();
            itr.remove();
        }
    }
		//当然我们也可以这么操作
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        for (int i = 3; i < 20; i++) {
            arrayList.add(i);
        }
        for (int i = 0; i < arrayList.size(); i++) {
            arrayList.remove(i);
        }
        //所以这两个基本一致

那么迭代器是如何运作删除的呢?
我们debug调试一下就可以发现,这个lastRet一直在-1和0之间徘徊,也就是说是从第一个数开始,不断前移,类似与pop这个功能,运行就像枪打出头鸟那种感觉。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值