02查看ArrayList源码和改进-顺序表-线性表-数据结构和算法

1 查看ArrayList源码

1.1 边界检测

​ 边界检测就是检测索引算法越界,方法中有索引的参数都需要检测。这里以add(int index, E e)和remove(int index)为例

  • add(int index, E e):这里我们要在指定索引index位置插入元素e,那么index就不能越界。因为是插入元素所以允许的范围就是 0 ∼ s i z e 0\sim size 0size,查看下源码实现如下:

    private void rangeCheckForAdd(int index) {
            if (index > size || index < 0)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    
    • 超出范围就报索引越界异常,是不是很熟悉😏
  • remove(int index):删除指定索引位置的元素。既然是删除元素,那么需要元素是数组中的一个,索引范围 0 ∼ s i z e − 1 0\sim size-1 0size1 。查看下ArrayList源码实现:

    private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    
    • 哎为啥这里不检测索引为负的情况呢?源码给出的解释:

    • 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.
      
    • 大意:此方法不检测索引是负数的情况。因为,一旦索引为负数,访问数组的时候就会立即抛出数组索引越界异常。

1.2 扩容

​ 在添加的元素的时候,怎么保证属性表容量是足够的呢?如果容量不够怎么解决呢?扩容策略是怎么样的呢?

那么来查看源码,源码区分首次添加元素扩容和非首次添加元素扩容:

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

    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)
            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;
    }
  • 分析:
  • 查看添加方法,ensureCapacityInternal(size + 1); 添加方法中,这里传递参数size+1指顺序表容量至少是size+1,因为要添加一个新元素,往下走
  • ensureCapacityInternal 方法首先判断是否是首次添加元素,如果是,容量取默认容量和方法参数中的最大值;如果不是首次添加元素,调用 ensureExplicitCapacity(minCapacity);
  • ensureExplicitCapacity方法中判断参数如果比数组长度小,那么表示容量不足,进行扩容,调用grow(minCapacity);
  • grow方法首先保存旧容量oldCapacity既数组的长度,新容量为旧的容量加上就容量的一半,即扩容策略就是扩容为原来容量的 3 2 \frac{3}{2} 23;判断如果新容量小于方法参数,把方法参数赋值给新容量;再判断新容量是不是大于数组最大大小,如果大于调用hugeCapacity(minCapacity);
  • hugeCapacity(minCapacity)方法先判断参数是否溢出,因为执行该方法就意味着参数大于MAX_ARRAY_SIZE,这是类中定义的常数:private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 如果溢出直接报OOM;没有在判断参数和MAX_ARRAY_SIZE的大小,前者大返回Integer.MAX_VALUE ;后者大,返回MAX_ARRAY_SIZE。
  • 继续返回grow方法,保证容量的前提下,实现数组数组迁移。
  • 继续返回add方法,保证容量前提下直接数组末尾插入,顺序表大小加1结束

如果后面有空画个流程图或者E-R图看起来会更直观。

1.3 foreach

​ 对于集合操作,其中之一遍历是很常用。我们都知道要实现foreach遍历需要实现iterator接口,我们查看ArrayList源码看看,它是怎么实现的?

public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

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

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
  • 通过iterator方法得到一个实现了Iterator接口的内部类,看下具体实现
  • 成员变量:
    • cursor:光标或称下一个元素索引
    • lastRet:上一个元素索引,默认-1
  • 成员方法
    • hasNext:cursor==size说明没有下一个元素了
    • next:
      • cursor赋值给i
      • 检测i范围
      • 如果i越界抛出相应异常;没有越界,cursor值加1,lastRet=i,返回该索引处元素
    • remove:
      • 判断如果lastRet小于0 抛出异常
      • try块尝试调用remove(lastRet)删除元素,成功lastRet赋值cursor,lastRet值置为-1;如果产生异常调整catch快。

3 关于modCount说明

​ 这个变量我们在ArrayList中发现很多方法中都有用到?那些方法嗯?有什么作用呢?

  • 位置:用的地方呢是改变ArrayList的方法,什么算是改变,如增删改排序等等,基本上除了查询外其他方法都有用到,即方法执行的时候会将该值加1。

  • 作用:这个变量用来做iterator变量时线程安全同步检测的。怎么实现的呢?就是上面说到当一个线程对该ArrayList对象做出改变的时候,modCount会加1;当另外一个线程也对同一对象做修改时,当前线程会检查该值是否改变,如果改变了就报错:ConcurrentModificationException (😏老弟又见面了),检测代码:

    int expectedModCount = modCount;
    final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
    

3 改进

​ 基本上是照搬的ArrayList源码,这里不再赘述,可以看下面仓库源代码,自行测试。

4 后记

​ 既然有官方实现,ArrayList功能还那么强大,为什么我们还有自己实现一个类似功能的集合类型呢?

  • ArrayList功能是很强大,但是不是所有的操作都是我们需要的,在某些应用场景下,使用起来很臃肿。
  • 某些应用场景我们想要添加新的功能,但是又需要ArrayList中的全部方法怎么办呢?

​ 上面这些情况都不适合用ArrayList。哎既然可以自己写,为啥还要参考ArrayList呢?ArrayList功能那么好,干嘛要自己去闭门造轮子呢?(😎自认为NB的除外)

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/algorithm

参考:

[1]百度百科.线性表[EB/OL].2022-05-08/2022-10-02.

[2]百度百科.顺序表[EB/OL].2022-05-09/2022-10-02.

[3]黑马程序员.黑马程序员Java数据结构与java算法全套教程,数据结构+算法教程全资料发布,包含154张java数据结构图[CP/OL].2020-01-18/2022-10-02.

[4]jdk ArrayList源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值