ArrayList源码分析

ArrayList

ArrayList是一个装载数据的数组集合,也可以说是动态数组。但我们先说一下 Java中的普通数组,Java中普通数组只能用来存储基本数据类型的数据,并且容量一旦定义了,就只能装这么多数据。但是ArrayList不是,它的容量能动态增长。允许包括 null 在内的所有元素。除了实现 List 接口外,它还提供一些方法来操作内部用来存储列表的数组的大小。(它大致上等同于 Vector 类,但是它不是线程安全的,而Vector是线程安全的)

结构

public class ArrayList<Eextends AbstractList<E>
        implements List<E>, RandomAccessCloneablejava.io.Serializable

从代码可以看出,它继承于AbstractList,并实现了List、RandomAccess、Cloneable、Serializable接口。

继承于AbstractList并实现了List接口:提供了添加、删除、修改、遍历等一些功能方法。

RandomAccess接口:随机访问功能,为List提供了快速访问功能,我们可以通过元素的下标快速获取元素对象,这就是快速随机访问。
没有定义任何代码,这种没有任何代码的接口在Java中被称为标记接口,用于声明类的一种属性。

public interface RandomAccess {
}

Collections中的binarySearch方法就根据list是否实现了RandomAccess而采用了不同的实现机制。ArrayList源码分析

Cloneable接口:重写了clone()函数,支持克隆。
Serializable接口:意味着ArrayList支持序列化传输。

ArrayList源码分析

基本用法

public boolean add(E e) // 添加元素
public boolean isEmpty() // 判断是否为空
public int size() //获取长度
public E get(int index) // 访问指定位置的元素
public int indexOf(Object o) // 查找元素,如果找到,返回索引位置,否则返回-1
public int lastIndexOf(Object o)  //是否包含指定元素,依据是equals方法的返回值
public E remove(int index) //删除指定位置的元素,返回值为被删对象
public boolean remove(Object o)// 删除指定对象,只删除第一个相同的对象,返回值表示是否删除了元素   如果o为null,则删除值为null的元素
public void clear() // 删除所有元素
public void add (int index,E element) //在指定位置插入元素,index为0表示插入最前面,index为ArrayList的长度表示插到最后面
public E set(int index,E element) //修改指定位置的元素内容

简单使用

ArrayList<Integer> integerList=new ArrayList<Integer>();
integerList.add(100); // 添加元素
integerList.add(0);
for(int i=0;i<integerList.size();i++){  // 遍历迭代
  System.out.println(integerList.get(i));
}

可以看出ArrayList的基本用法是比较简单的,它基本原理也是比较简单的。下面让我们看看它的一些属性值。

属性

    private static final long serialVersionUID = 8683452581122892189L;
    private static final int DEFAULT_CAPACITY = 10;  // 默认容量为10
    private static final Object[] EMPTY_ELEMENTDATA = {}; // 空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData; // 暂时忽略transient(不可被序列化)这个关键字,elementData会随着实际元素的个数增多而重新分配
    private int size; // size则记录实际的元素个数

构造函数

当我们用下面的方式创建一个集合时,那么它会默认选择使用这个构造方法来创建集合。
这个elementData是一个Object类型的数组,至于这个DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个是一个空数组。这里要记住,它并没有初始化数组,所以现在的容量还为0,当我们去调用add()方法时才会进行初始化

// 源码
public ArrayList() 
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

------------------------------------------------------------------------------------
public class ArrayListTest01 {
    public static void main(String[] args) {
        ArrayList arr=new ArrayList();
    }
}

当我们传入一个容量值时,使用的是这一个构造方法。

public ArrayList(int initialCapacity) {
        // 传入容量大于0,则进行初始化数组
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        // 传入容量为0,则会引用一个空数组对象
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        // 传入容量小于0,则会抛出参数异常
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

提供一个集合作为参数。会把集合中的所有元素复制到数组中

public ArrayList(Collection<? extends E> c) {
        // 将当前集合元素给到一个Object类型的数组
        Object[] a = c.toArray();
        // 判断集合的长度是否为0
        if ((size = a.length) != 0) {
            // 查看当前集合是否为ArrayList类型
            if (c.getClass() == ArrayList.class{
                elementData = a;
            } else {
            // 创建一个新的数组
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            // 如果集合长度为0,那么就是一个空数组
            elementData = EMPTY_ELEMENTDATA;
        }
    }

add方法

modCount:表示内部修改的次数,为什么要记录修改次数,我们待会解释。

public boolean add(E e) {
        // 校验是否扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 插入元素到数组,并且size++,size是记录元素个数的,谨记!!!
        elementData[size++] = e;
        // 如果插入成功,返回true
        return true;
    }
    
private void ensureCapacityInternal(int minCapacity) {
        // 保证明确的容量              计算容量
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
// 计算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 如果elementData为空数组,那么赋予它一个默认的容量为10,这里也就是为什么说它默认容量为10的原因。当我们以ArrayList arr=new ArrayList();这种方式初始化一个容器时,看第一个构造方法,会赋予它一个空数组,以至于这里判断,所以赋予它一个为10的默认容量。
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

private void ensureExplicitCapacity(int minCapacity) {
        // 记录内部的修改次数(增加)
        modCount++;

        // overflow-conscious code
        // 如果当前需要的容量大于数组长度,会进行扩容
        if (minCapacity - elementData.length > 0)
        // 扩容,这里暂且不讲,下面会有讲到
            grow(minCapacity);
    }
    

在指定位置,插入元素

public void add(int index, E element) {
        // 首先检查下标,下标不能大于元素数量以及不能小于0,否则会抛出数组下标越界异常
        rangeCheckForAdd(index);
        // 校验是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 插入时,插入位置之后的元素需要进行后移操作
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 插入元素到数组
        elementData[index] = element;
        // size进行++操作
        size++;
    }
    
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

remove方法

 public E remove(int index) {
        // 校验我们要删除元素的索引下标是否超过了当前元素数量,如果超过了抛出索引越界异常
        rangeCheck(index);
        // 删除也算是一种结构变化,所以也要进行++操作
        modCount++;
        // 拿到要删除的元素,之后会将这个删除的元素进行返回
        E oldValue = elementData(index);
        // 计算要移动的元素个数, 例如 元素为0,1,2,3,4,5 ,如果我们删除第3个元素,从0开始,size为6,6-3-1=2,那么需要往前移动2个元素,也就是4,5这两个元素需要被移动。
        int numMoved = size - index - 1;
        // 如果大于0,说明要移动元素。这么为什么要大于0,因为如果我们删除最后一个元素时,根本不需要移动。
        if (numMoved > 0)
        // 参数说明:
              // 1.原数组
              // 2.原数组开始的位置,根据要移动元素的个数来确定移动几个元素,放到目标数组当中开始的位置
              // 3.目标数组,目标数组开始的位置
              // 4.移动元素的个数
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 将最后一个元素位置置为null,等待垃圾回收处理
        elementData[--size] = null// clear to let GC do its work
        // 返回删除的元素
        return oldValue;
    }
    
private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
// 获取到要删除的元素
elementData(int index) {
        return (E) elementData[index];
    }

remove(Object o)方法

public boolean remove(Object o) {
        // 判断传进来的参数是否为null
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
        // 如果不为null
            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);
        // 删除某个元素,最后一个元素置为null,等待垃圾线程进行回收
        elementData[--size] = null// clear to let GC do its work
    }

扩容

// 扩容方法
private void grow(int minCapacity) {
        // overflow-conscious code
        // 获取当前数组长度
        int oldCapacity = elementData.length;
        // 右移1,也就是除以2^1加上原来的数组长度,也就是扩容原来的1.5倍,比如默认10个数组长度装不下了,那么下次就扩容为15.
        // 这里的位移与jdk1.7的源码不同,jdk1.7没有采用位移运算
        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 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;
    }

get方法

public E get(int index) {
      // 校验数组下标
        rangeCheck(index);
      // 通过数组下标返回元素
        return elementData(index);
    }
    

private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

set方法

为什么指定位置修改元素,没有用到modCount? 因为修改元素,数组的结构并没有任何变化。

public E set(int index, E element) {
        // 校验数组下标
        rangeCheck(index);
        // 获取要替换的数组元素
        E oldValue = elementData(index);
        // 将此位置上的元素,替换为我们要修改的元素
        elementData[index] = element;
        // 返回旧值
        return oldValue;
    }

contains(Object o)

public boolean contains(Object o) {
        // 调用了indexOf()方法,如果不返回-1,说明此元素存在
        return indexOf(o) >= 0;
  }

indexOf()方法

public int indexOf(Object o) {
        // 首先校验是否为null
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        // 如果不为null,循环遍历元素,找到后,返回元素下标
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        // 如果没有找到返回-1
        return -1;
    }

好了,以上就是在操作ArrayList集合时,一些常用的操作方法。

下面我们来看一下迭代以及modCount的作用

迭代

下面这个迭代是我们对ArrayList一个常见操作,循环打印ArrayList中的每个元素

ArrayList<Integer> arr=new ArrayList<Integer>();
        arr.add(123);
        arr.add(456);
        arr.add(789);
        for (Integer number : arr) {
            System.out.println(number);
        }

当然,也不止这一种方式,还有下面这一种。

ArrayList<Integer> arr=new ArrayList<Integer>();
        arr.add(123);
        arr.add(456);
        arr.add(789);
        for (int i = 0; i < arr.size(); i++) {
            System.out.println(arr.get(i));
        }

对比以上两种方式,还是foreach上更为简洁一些,而且这种方式适用于各种容器,更为通用。

foreach背后实现方式

foreach语法背后是怎么实现的呢?其实在我们去编译代码的时候,它会转换为这种方式。

 ArrayList<Integer> arr=new ArrayList<Integer>();
        arr.add(123);
        arr.add(456);
        arr.add(789);
        Iterator<Integer> iterator = arr.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

ArrayList实现了Iterable接口,Iterable表示可以迭代。定义很简单,就是要求实现iterator方法。返回了一个Iterator接口的对象

只要对象实现了Iterable接口,就可以使用foreach语法,编译器就转换为调用Iterable和Iterator接口的方法。初次见到Iterable和Iterator可能会容易混淆。我们来看看它们的作用

  • Iterable表示对象可以被迭代,它有一个方法iterator(),返回Iterator对象,实际通过Iterator接口的方法进行遍历。
  • 如果对象实现了Iterable,就可以使用foreach用法。
  • 类可以不实现Iterable,也可以创建Iterator对象。

Iterable接口

iterator为迭代器对象,对集合进行遍历的底层依赖,iterable接口是所有Collection类的父接口,内部封装了Iterator迭代器对象,因此集合类可以使用迭代器遍历元素。

public interface Iterable<T{
    
    Iterator<T> iterator();
      
    // 以下两个默认方法是在Java 8添加进来,因为在Java 8以前,接口不能有任何实现方法。只能有抽象方法和常量
    // 
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    // 为遍历和分隔元素提供更便捷的操作
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

Iterator接口

这个接口jdk1.8与jdk1.8之前有不同的地方。remove()方法有所不同

public interface Iterator<E{
    // 判断是否元素未访问
    boolean hasNext();
    // 返回下一个元素
    next();
    // void remove(); jdk1.8之前的
    // 下面两个默认方法在jdk1.8 才有的
    default void remove() {
    // 无支持的操作异常
        throw new UnsupportedOperationException("remove");
    }
    // 这里封装了hasNext()以及next()方法,并且提供了null值的判断,利用lambda表达式表达式进行函数式接口的调用,可以在任何类中直接使用更简洁的语法对元素进行访问操作。
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
    
    // 对null值的判断
    public static <T> requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

ListIterator

除了iterator(),ArrayList还提供了两个返回Iterator接口的方法:增加了一些方法,如:向前遍历、添加元素、修改元素、返回索引位置等

public ListIterator<E> listIterator() {
        return new ListItr(0);
    }
public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }
public interface ListIterator<Eextends Iterator<E{
    // 判断是否元素未访问
    boolean hasNext();
    // 返回下一个元素
    next();
    // 判断前面元素是否未访问
    boolean hasPrevious();
    // 返回上一个元素
    previous();
    // 返回下一个元素的索引位置
    int nextIndex();
    // 返回上一个元素的索引位置
    int previousIndex();
    // 删除
    void remove();
    void set(E e);
    void add(E e);

listIterator():方法返回的迭代器从0开始,而listIterator(int index)方法返回的迭代器从指定位置index开始。例如:比如从末尾往前遍历。

@Test
    public void test04(){
        ArrayList<Integer> arr=new ArrayList<Integer>();
        arr.add(123);
        arr.add(456);
        arr.add(789);
        ListIterator<Integer> integerListIterator = arr.listIterator(arr.size());
        while(integerListIterator.hasPrevious()){
            System.out.println(integerListIterator.previous());
        }
    }

Fail-Fast(快速失败)机制

Fail-Fast简介

快速失败指的是某个线程在迭代集合类的时候,不允许其他线程修改该集合类的内容,这样迭代器出来的结果就不会准确。它是一种错误机制,只能被用来检测错误,因为JDK并不保证fail-fast机制一定会产生。


关于迭代器,有一种常见的误用,就是在迭代的中间调用容器的删除方法,比如,要删除一个整数ArrayList中所有小于100的数,我们可能会这么写:

@Test
    public void test05(){
        ArrayList<Integer> arr=new ArrayList<Integer>();
        for (int i = 0; i < 100; i++) {
            arr.add(i);
        }
        for (Integer num : arr) {
            if(num<100){
                arr.remove(num);
            }
        }
    }
    
实际编译器编译后大概是这样子的:
@Test
    public void test08(){
        ArrayList<Integer> arr=new ArrayList<Integer>();
        for (int i = 0; i < 100; i++) {
            arr.add(i);
        }
        Iterator<Integer> iterator = arr.iterator();
        while(iterator.hasNext()){
            if(iterator.next()<100){
                // 此方法是ArrayList的remove()方法
                arr.remove(iterator.next());
            }
        }
    }
    
随后抛出异常
java.util.ConcurrentModificationException
 at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
 at java.util.ArrayList$Itr.next(ArrayList.java:861)
 at 集合.ArrayListTest01.test05(ArrayListTest01.java:74)

发生了 并发修改异常,为什么呢?
因为迭代器内部会维护一些索引位置相关的数据,要求在迭代过程中,容器不能发生结构性变化,否则这些索引位置就失效了。所谓的结构性变化就是添加、插入、删除元素,修改元素内容不算结构性变化,这也是上文中提到的modCount的作用。

我们先来看正确的删除方法,如何避免异常的发生,稍后在分析出现异常的原因。

@Test
    public void test06(){
        ArrayList<Integer> arr=new ArrayList<Integer>();
        for (int i = 0; i < 100; i++) {
            arr.add(i);
        }
        Iterator<Integer> iterator = arr.iterator();
        while(iterator.hasNext()){
            if(iterator.next()<100){
                // 这里的remove()方法与上文中出现异常的remove()方法不同
                // 一个是ArrayList的remove方法,这个是iterator的remove方法
                iterator.remove();
            }
        }
    }

那么迭代器如何知道发生了结构性变化,并抛出异常?它自己remove()方法为什么可以使用?expectedModCount可以检测出结构是否发生变化。

到这里后,大家可以再去看看ArrayList的remove()方法,里面有一个fastRemove()方法会修改modCount的值。如果我们使用ArrayList的remove()方法,那么在我们去删除元素的时候,modCount会有所变化。当我们在使用迭代器时,会创建一个Itr()对象,初始化cursor、lastRet、expectedModCount的值,此时expectedModCount等于当前的modCount。在迭代元素的时候,expectedModCount不会发生任何变化,迭代过程中,会进行结构是否变化的判断,如果modCount和expectedModCount不相等就会抛出并发修改异常!!

public Iterator<E> iterator() {
        return new 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;
      }

final void checkForComodification() {
          // 在我们去迭代元素时 ,会检查结构是否发生变化。
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

我们点击下面 报错的信息,861行。

迭代器的实现原理

private class Itr implements Iterator<E{
        // 表示下一个要返回的元素位置
        int cursor;       // index of next element to return
        // 表示最后一个返回的索引位置,如果没有返回-1
        int lastRet = -1// index of last element returned; -1 if no such
        // 期望修改的次数,初始化为外部的modCount,成员内部类可以直接访问外部类的实例变量,每次发生结构性变化时modCount都会进行++,而每次迭代器操作的时候都会检查expectedModCount是否与modCount相同,这样就能检测出变化
        int expectedModCount = modCount;

        Itr() {}
        // 如果下一个元素位置不等于size元素数量,说明后面还有元素,继续遍历
        public boolean hasNext() {
            return cursor != size;
        }
        // 获取下一个元素
        // 压制警告
        @SuppressWarnings("unchecked")
        public E next() {
            // 检查结构是否变化
            checkForComodification();
            // i为元素下标
            int i = cursor;
            // 检查i是否大于了size的元素数量,如果大于了,说明后面没有元素,直接抛出异常
            if (i >= size)
                throw new NoSuchElementException();
            // 
            Object[] elementData = ArrayList.this.elementData;
            // 在遍历的过程中,可能会有添加 插入,需要判断。 
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            // 下一个元素的下标
            cursor = i + 1;
            // lastRet记录最后一个返回的索引位置,所以必须跟i看齐,返回对应的元素
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            // 如果删除时,ArrayList中没有元素,lastRet为-1,不能删除,抛出异常
            if (lastRet < 0)
                throw new IllegalStateException();
            // 检查结构是否有所变化
            checkForComodification();
            try {
                // 调用了ArrayList的remove()方法
                ArrayList.this.remove(lastRet);
                // 同时更新cursor、lastRet、expectedModCount的值
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            // 判断集合是否为null,如果为null,抛出空指针异常
            Objects.requireNonNull(consumer);
            // 当前ArrayList的size数量
            final int size = ArrayList.this.size;
            // 下个元素的坐标
            int i = cursor;
            // 如果i大于size的元素数量直接return,因为已经没有元素了
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            // 如果在遍历时,进行了添加插入操作,i可能会大于数组长度
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            // 当i不等于size元素数量以及期望修改的次数相等,则获取元素
            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() {
        // 这里就是上文中的出现异常的地方,911行
        // 如果modCount不等于expectedModCount就会出现并发修改异常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

ArrayList源码分析而这个remove方法,里面虽然也是用ArrayList的remove()方法,但是对cursor、lastRet、expectedModCount做了及时更新,所以不会出现并发修改异常。

不过,需要注意在调用remove()方法前必须先调用next()方法。这个方法是将所有元素删除,我们也可以直接用现成的clear()方法。

@Test
    public void test07(){
        ArrayList<Integer> arr=new ArrayList<Integer>();
        for (int i = 0; i < 100; i++) {
            arr.add(i);
        }
        Iterator<Integer> iterator = arr.iterator();
        while(iterator.hasNext()){
          // 如果不调用next()方法会抛出异常
            //iterator.next();
            iterator.remove();
        }
    }
    
    java.lang.IllegalStateException
 at java.util.ArrayList$Itr.remove(ArrayList.java:874)
 at 集合.ArrayListTest01.test07(ArrayListTest01.java:103)
ArrayList源码分析

ArrayList 采用了快速失败的机制,通过记录 modCount 参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

迭代器的好处

为什么要通过迭代器这种方式访问元素呢?直接get(index)语法不是也可以嘛?之前我也很质疑。

但是在一些场景下,确实没有什么区别,两者都可以。不过,foreach语法更为简洁一些,更重要的是,迭代器语法更为通用,它适用于各种容器类。需要访问容器元素的代码只需要一个Iterator接口的引用,不需要关注数据的实际组织方式,可以使用一致和统一的方式进行访问。

在ArrayList中,get(index)语法和迭代器性能是差不多的,但是在后续介绍的其他容器中,则不一定,比如LinkedList,迭代器性能就要高很多。

迭代器表示的是一种关注点分离的理想,将数据的实际组织方式与数据迭代遍历相分离,是一种常见的 设计模式。—来自《Java编程逻辑》一书

总结

后续我会介绍各种容器和数据的组织方式,每一种不同的方式都有不同的特点,而不同的特点就有不同的使用场合。性能是其中一个很重要的一个部分。但是对于数据结构来说,性能高,就不安全;性能低,那么就安全。对于ArrayList来说,它不是线程安全的,插入和删除效率低,它的特点如下:

  • 可以随机访问,按照索引位置进行访问,直接一步到位。时间复杂度为O(1)。
  • 按照内容查找元素效率比较低,有n个元素,就遍历n个元素。时间复杂度为O(N),性能与数组的长度为正比。
  • 在数组末尾添加元素为O(1)。添加N个元素为O(N)。
  • 插入和删除元素的效率比较低,因为需要移动元素。时间复杂度为O(N)。
  • ArrayList的默认容量为什么是10,这个。。。据说是因为sun的 程序员对一系列广泛使用的程序代码进行了调研,结果就是10这个长度的数组是最常用的最有效率的。也有说就是随便起的一个数字,8个12个都没什么区别,只是因为10这个数组比较的圆满而已。。。。。
  • 在我们使用new ArrayList()时或者new ArrayList(int initialCapacity) 刚开始并没有进行初始化数组。大家可以自行去试。
@Test
    public void test09(){
        ArrayList arr1=new ArrayList();
        System.out.println(arr1.size());
        arr1.set(0,1);
        ArrayList arr=new ArrayList(10);
        System.out.println(arr.size());
        arr.set(0,2);
    }

希望大家有所收获,学生党知识有限!!


原文始发于微信公众号(阿黄学编程): ArrayList源码分析

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/35583.html

(0)
小半的头像小半
0 0

相关推荐

  • 除了 this 指向,你对箭头函数还了解多少 技术漫谈

    除了 this 指向,你对箭头函数还了解多少

    0 0137
    小半的头像 小半
    2024年3月11日
  • 深入解析HashMap的面试题与答案 微信精选

    深入解析HashMap的面试题与答案

    0 0236
    小半的头像 小半
    2023年8月17日
  • 史上最全,全方位阐述 SpringBoot 中的日志是怎么工作(珍藏版) Java知音

    史上最全,全方位阐述 SpringBoot 中的日志是怎么工作(珍藏版)

    0 0175
    Java知音的头像 Java知音
    2024年4月11日
  • SpringBoot 通用限流方案(VIP珍藏版) Java知音

    SpringBoot 通用限流方案(VIP珍藏版)

    0 0217
    小半的头像 小半
    2023年6月29日
  • 超实用的Chrome DevTools调试技巧! 前端开发

    超实用的Chrome DevTools调试技巧!

    0 0196
    小半的头像 小半
    2022年11月4日
  • 前端更新部署后通知用户刷新 技术分享

    前端更新部署后通知用户刷新

    0 0255
    李, 若俞的头像 李, 若俞
    2024年4月5日
  • 数据采集之Web端导入DB数据到HadoopHDFS

    数据采集之Web端导入DB数据到HadoopHDFS

    0 0150
    小半的头像 小半
    2022年5月7日
  • 探索 Compose 内核:深入 SlotTable 系统 Android

    探索 Compose 内核:深入 SlotTable 系统

    0 0296
    小半的头像 小半
    2022年11月25日
  • SpringCloud注册与发现之Zookeeper 技术漫谈

    SpringCloud注册与发现之Zookeeper

    0 0190
    小半的头像 小半
    2024年3月9日
  • 有点强!excel的导入导出用它就够了! 技术分享

    有点强!excel的导入导出用它就够了!

    0 0191
    Java光头强的头像 Java光头强
    2024年3月26日
  • 用ChatGPT生成Excel公式,太方便了! Java知音

    用ChatGPT生成Excel公式,太方便了!

    0 0229
    小半的头像 小半
    2023年2月9日
  • 彻底改变AI和机器学习界的13个开源项目 开源速递

    彻底改变AI和机器学习界的13个开源项目

    0 0196
    小半的头像 小半
    2023年2月12日

发表回复

登录后才能评论

扫码关注公众号,技术文章第一时间送达

ArrayList源码分析

站长精选

  • CTO:禁止在项目中使用 BeanUtils 属性转换工具。。。

    CTO:禁止在项目中使用 BeanUtils 属性转换工具。。。

    2023年1月6日

  • 最强 Linux 命令总结(特别推荐版)

    最强 Linux 命令总结(特别推荐版)

    2023年6月18日

  • Prometheus + Grafana,开源监控神器!

    Prometheus + Grafana,开源监控神器!

    2023年4月19日

  • 还在用 if 做条件验证?来试试用 @Validated,这样写更优雅!

    还在用 if 做条件验证?来试试用 @Validated,这样写更优雅!

    2023年8月10日

  • 告别付费软件,这款开源工具可以随时随地监控和控制所有设备!

    告别付费软件,这款开源工具可以随时随地监控和控制所有设备!

    2023年8月3日

  • Java 淘宝秒杀脚本(已自测)

    Java 淘宝秒杀脚本(已自测)

    2023年3月13日

  • 你的 IDE 太重了,试试 Emacs吧!

    你的 IDE 太重了,试试 Emacs吧!

    2023年3月13日

  • 开源一个网页版的 Xshell,支持 FTP 和 SFTP 两种方式!

    开源一个网页版的 Xshell,支持 FTP 和 SFTP 两种方式!

    2023年6月18日

  • 一篇文章搞定,多线程常见锁策略+CAS

    一篇文章搞定,多线程常见锁策略+CAS

    2023年3月7日

  • 12个超好用的免费在线工具,大大提高生产力,建议收藏!

    12个超好用的免费在线工具,大大提高生产力,建议收藏!

    2023年4月25日

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!

深圳SEO优化公司淄博网站制作网络优化盐城市网站优化渠道南昌网站排名优化公司电子商务网站网络优化分析玉田专业网站优化seo网站优化必备技巧关键词对网站优化有何作用福州网站seo推广优化泊头网站优化遂宁网站seo优化公司保定网站搜索排名优化要多少钱重庆网站优化简历黄浦区搜索引擎网站优化方案如何优化电影网站提高流量深圳网站快速优化工具网站怎么广告优化网站的内部优化包括网站标题优化网站优化软件费用多少许昌知名网站优化哪家好优化网站速度慢怎么办端州网站建设优化网站seo优化排名需要多久佛山涂料网站seo优化怎么优化网站30云刂速刂捷济南软件优化网站行业网站如何优化新网网站内部优化商城性质的网站怎么优化沁阳怎么优化网站想找个兼职去哪个网站优化歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化