ArrayList源码详解

本文详细解析了ArrayList的实现原理,包括其基于数组的存储方式、默认容量、空数据区别、成员变量及作用。介绍了ArrayList的构造方法,如无参构造、带容量参数构造和以集合为参数的构造。还深入探讨了add方法、扩容机制grow方法以及get方法的工作流程。此外,迭代器的使用和其内部变量也进行了说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Collection.List.ArrayList详解

简介:

ArrayList底层是基于数组存储的。它继承了AbstractList类。实现了RandomAccess,Cloneable和java.io.serializable接口。通过上三个接口它实现了快速访问,赋值和序列化的功能。

成员变量

  1. serialVersionUID:
    适用于java序列化机制中,用来验证版本一致性,一致则可以进行反序列化。
  2. DEFAULT_CAPACITY:
    默认容量,值是10.
  3. EMPTY_ELEMENTDATA:
    空数据,当有参构造方法的参数为 “0” 时使用该变量给elementData赋值。
  4. DEFAULTCAPACITY_EMPTY_ELEMENTDATA:
    空数据,当无参构造方法使用时,使用该变量给elementData赋值。
    两个空数据的区别:使用无参构造方法时会使用默认容量,两个空数据就是区分使用的那个构造方法构建的。以确定初次扩容的时候怎么扩容。
    注意:arrayList中使用无参构造方法时,使用默认容量,但是,它创建的还是一个空数据,只有当第一次添加数据时才会使用默认容量,进行扩容。
  5. elementData:用于存放数据的数组。
  6. size:ArrayList的容量。
  7. MAX_ARRAY_SIZE:数组的最大容量,其值是int的最大值减8,那8个容量是用来存放数组中的length字段的。
  8. modcount:
    因为arrayList是线程不安全的,为了保证在迭代器使用时不会出现越界而设置,因为如果迭代器开始使用后,其它线程将数组删除了几个元素就会出现越界。迭代器每次使用modcount都会加1,之后会进行对比,确保modcount没有被修改,只要不被修改就代表数组没有被修改。

方法

构造方法

  1. 无参构造方法

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

无参构造方法是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData

  1. 有容量参数的构造方法
/**
     * 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);
        }
    }

参数initialCapacity代表需要的容量,首先如果大于零创建一个指定大小的数组,如果等于零将EMPTY_ELEMENTDATA赋值给elementData,否则就是容量小于零,抛出异常。

  1. 以一个集合为参数的构造方法
public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }

讲解:该方法中首先使用传进来的集合的toArray方法将其转换为数组,然后如果a也就是c不为空就进入if内部。
之后判断c的Class对象和ArrayList的Class对象相同是否相同?
这个判断为什么会存在?因为所有继承Collection的类都可以重写toArray方法,所以他们的返回值就不一定是Object数组了,所以要进行判断。
如果相同之间赋值,不同就用copeof方法拷贝一份。

add方法

add方法有四种:
1、add(E e);
2、add(int index, E element);
3、addAll(Collection<? extends E> c);
4、addAll(int index, Collection<? extends E> c);

主要讲解2和4:

  1. 在指定位置添加一个元素。
/**
     * 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++;
    }

	

讲解:该方法传入两个参数,一个位置,一个要添加的数据。
该方法首先会对要添加的位置进行检查,确定位置的合法性。
然后,对容量进行检查判断是否需要扩容,如果需要就扩容。
之后的System.arraycopy(),是将elementData从index位置开始复制,复制到elementData中从index+1开始粘贴,复制size-index位。

/**
     * A version of rangeCheck used by add and addAll.
     */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)//保证插入位置的正确性。
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

	private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }//calculateCapacity用来计算容量。

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//该方法是用来扩容的,放在后面讲。
    }
  1. 在指定位置添加整个集合
public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

讲解:
经过上面几个方法的讲解相信大家基本一会自己分析了,
首先判断添加的位置合法性。
然后将集合转换为数组,
再获取要添加的长度,并判断是否需要扩容,
最后复制

grow方法

该方法用来扩容

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

进入该方法首先将数组的长度存在oldCapacity中,之后进行扩容扩展到1.5倍,
但是扩展之后的容量可能还不够,如果不够就直接将需要的值赋给newCapacity,然后再判断newCapacity与数组最大容量的大小,如果newCapacity更大就直接将整数最大值赋给它。
如果还不够就没办法了。

get方法

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

        return elementData(index);
    }

因为ArrayList底层是数组实现的所以可以直接通过下标获取元素。

迭代器:iterator

迭代器与foreach非常相似。
迭代器里面定义了三个变量:

		int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

cursor是下一个要访问元素的位置
lastRet是上一个要访问元素的位置
expectedModCount:代表对 ArrayList 修改次数的期望值,初始值为 modCount。
其方法有hasNext,next,remove
next:

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

判断下标是否合法,如果合法cursor变为下一个要访问的元素的下表值,返回当前值并且将上一个要返回的值的下表lastRet加一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值