Java源码阅读之ArrayList(数组扩容,常用方法,迭代器)

Java源码阅读之ArrayList

1.ArrayList类介绍

​ 用可调整数组( Resizable-array )实现接口,实现了所有对列表的操作,而且允许存放所有元素包括null。除了是实现接口之外,此类提供了一些方法来操作列表(List)的内部数组(array)的大小(size)来操作列表。这个类大致和Vector类是一样的,但它是不同步的(unsynchronized)意味着线程不安全。

​ size(),isEmpty(),get(),set(),iterator(),listIterator()这些操作是运行在固定时间 (constant time)。add()操作运行在分摊常量时间(amortized constant time),也就是说添加n个元素需要O(n)时间,其他所有的操作运行在线性时间(线性时间),常数因子低于LinkedList。

​ 每个ArrayList实例都有一个(容量),容量是List中存放元素的数组的大小,容量至少和list的大小一样大(array.length>=list.size).当元素被添加到ArrayList中时,容量会自动增长。扩容策略的细节

源码解析

ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
//默认的初始化容量
private static final int DEFAULT_CAPACITY = 10;
//存储ArrayList元素的数组缓冲区,ArrayList的容量是这个数组缓冲区的长度。所有的空Arrylist的
//elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA(一个空数组),当第一个元素添加时,将
//将扩展到DEFAULT_CAPACITY(默认容量为10)
//被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化

transient Object[] elementData;
//ArrayList的大小(它包含的元素个数)
//注意容量和大小是两个概念
private int size;

3个构造方法:

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);
    }
}
//构造一个初始容量为10的空列表。(源码注释这么说的,感绝不太对,也可能他的意思DEFAULT_CAPACITY是10,并不是数组缓冲区大小就是10)
//看上面elementData的注释,空的list因该是0,添加第一个元素时,变成10.
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//传入一个集合
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;
    }
}

//将这个ArrayList<实例的容量调整为列表的当前大小
//modCount是继承父类AbstractList的,结构上发生变化时++ 
//protected transient int modCount = 0;
public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}
//增加这个ArrayList实例的容量,以确保指定最小容量参数,可以容纳所有元素。
//手动扩容
public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // 除了不是默认容量的空数组外任意大小的数组
        ? 0
        //默认容量空数组(空参构造出来的)这里感觉和上面提到了默认容量是10有点关系,如果对默认容量的空数组扩容,还是默认容量10.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

//确定扩容后容量大小的数组扩容方法
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 扩容后大小(要扩大到多大)要大于现在数组长度
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

//ArrayList中真正实现数组的方法
private void grow(int minCapacity) {
        // 数组原长度
        int oldCapacity = elementData.length;
    	//默认新长度扩容,原长度二进制右移一位,相当于oldCapacity/2也就是扩容1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)//默认规则扩容后比给定扩容量小
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)//扩容后超出最大数组容量
            newCapacity = hugeCapacity(minCapacity);
    	//数组扩容的本质是拷贝一个新数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

//要分配的数组的最大大小。一些vm在数组中保留一些头字。尝试分配更大的数组可能会导致OutOfMemoryError:请求的数组大小超过VM限制
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

//int的最大常量值2^31-1
@Native public static final int   MAX_VALUE = 0x7fffffff;

//超大容量,返回一个能扩容的最大容量
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

//返回list中的元素个数
public int size() {
    return size;
}
//list判断是否为空
public boolean isEmpty() {
    return size == 0;
}
//判断是否包含元素
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

//通过循环遍历数组查询是否包含此元素并返回数组下标,没有则返回-1,注意这里是可以对null进行查找的
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;
    }
//判断元素最后一次出现的位置,原理是数组的反遍历
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;
}
//返回Arraylist实例的浅拷贝(元素本身没有拷贝),(源码注释是这样写的,但是咱们说是深拷贝)
//Arrays.copyOf()是创建一个新的数组(也就是分配了一个新的内存空间),然后调用System.arraycopy()复制内容,赋值给新数组,然后返回新数组。
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);
    }
}

关于数组拷贝的一些细节:

Arrays.copyOf()是创建一个新的数组(也就是分配了一个新的内存空间),然后调用System.arraycopy()复制内容,赋值给新数组,然后返回新数组。

如果要赋值的数组存的基本数据类型(int、float、double等等),那么数组的每个元素存的就直接是数值,所以赋值后,得到的是个独立全新的副本,操作副本,不会影响原来的数组。

如果数组存的是对象的话,那么数组每一个元素存的就是对象的引用,所以复制后,得到了一个新的引用副本,操作副本就等于改变了原来的数组。

因为Java 虚拟机内存上就是这样分配的,栈帧中,只会直接为基本数据类型分配空间(比如 int ,float),然后直接将数据的值存在栈帧中,而对于对象,那么只会在栈帧中分配一个引用的大小,存储对象的引用就行了。

//返回一个新的对象数组,返回的数组将是“安全的”,因为没有list的引用。(换句话说,此方法必须分配新数组)。因此,调用者可以自由修改返回的数组。
public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}
//传一个数组,如果a数组长度大于list的size则将数组中数据copy进去,多余的填充null。注意会覆盖原数组。
//如果传入数组长度小于list大小,则创建新的数组。
//用法可以在调用时传入 new一个空数组,然后定义一个数组去接返回值。People[] bb =list2.toArray(new People[0])长度任意
 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;
    }

下面是一些常用操作list的方法:

get( )方法:

 E elementData(int index) {
        return (E) elementData[index];
    }
//返回此列表中指定位置的元素
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}


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

set( )方法:返回被替代的元素

//将此列表中指定位置的元素替换为指定元素
public E set(int index, E element) {
    rangeCheck(index);

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

add( )方法:一个核心方法,为什么说是核心呢,add方法需要保证底层数组正确性。可以看看下面关于的源码。

//将指定的元素追加到此列表的末尾。
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 确保容量足够,不够会扩容
    elementData[size++] = e;//放入元素
    return true;
}
//首先需要保证内部容量,调用分析(从下往上)
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

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

        // 计算后的容量大于本数组的长的长度(说明元素个数大于底层数组的长度,需要扩容)
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//详情看上面数组扩容
    }

//计算出容量
 private static int calculateCapacity(Object[] elementData, int minCapacity) {
     //elementData :该列表的数组 minCapacity:size+1(确保加一个是可以加进去的)
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//是否为初始化空list
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }


//在数组中的指定位置插入指定的元素列表。如果该位置有元素,将该元素和右面的所有元素右移一位,将指定元素插入

// 实现add的步骤 范围检查(添加的指定位置) -->确保容量 -->数组拷贝(右移) -->插入目标元素 --> 列表大小(元素个数)+1
public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // 确保容量足够,不够会扩容,详情参照上方添加
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);//向右移动其实是进行数组拷贝,此时数字大小经过上面已经可以保证足够
    elementData[index] = element;//将目标元素放入指定位置
    size++;//list的大小+1
}

//本地方法,参数可以看出方法作用,想象一下复制粘贴。参数:(源数组或者说被拷贝数组,拷贝开始的位置,目标数组,粘贴开始的位置,需要拷贝的长度或者说个数)
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
//检查添加位置没有越界
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

remove( )方法:根据索引移除,和根据元素移除


//实现步骤:检查范围--> 确定需要移动元素范围--> 数组拷贝(左移)-->移除数组中多余的元素+list的大小-1-->返回移除的元素
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);
    	//移动完后,会多一位,即最后面的一位元素。这行代码两个作用,1.移除数组中多余的元素2.list的大小-1
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

//遍历移除第一次找到的元素
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;
}

//私有移除方法,它跳过边界检查,不返回移除的值。原理和index移除基本一样
 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
    }
//清空数组中内容,将list的元素个数置为0
public void clear() {
    modCount++;

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

    size = 0;
}
//实现步骤:集合转数组--> 确定容量--> 数组copy -->增加list的size
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

//类似与add
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;
    }

迭代器:

迭代器是一种模式、详细可见其设计模式,可以使得序列类型的数据结构的遍历行为与被遍历的对象分离,即我们无需关心该序列的底层结构是什么样子的。只要拿到这个对象,使用迭代器就可以遍历这个对象的内部

实现了Collection接口的集合类,都有一个Iterator方法,用于返回一个实现了Iterator接口的对象,用于遍历集合;(Iterator接口定义了3个方法分别是hasNext(),next(),remove();)

对List来说,你也可以通过listIterator()取得其迭代器,两种迭代器在有些时候是不能通用的,Iterator和ListIterator主要区别在以下方面:

1. iterator()方法在set和list接口中都有定义,但是ListIterator()仅存在于list接口中(或实现类中);

2. ListIterator有add()方法,可以向List中添加对象,而Iterator不能

3. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。

4. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。

5. 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。

因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。其实,数组对象也可以用迭代器来实现。

ArrayList中有两个内部类分别实现了ListIterator和Iterator接口

//这是它们的构造方法:
public ListIterator<E> listIterator() {
    return new ListItr(0);
}

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

下面我们看看具体的实现类:

private class Itr implements Iterator:


//An optimized version of AbstractList.Itr(源码注释说优化了父类AbstractList的迭代器)
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
    int expectedModCount = modCount;//modCount是list中数组改变次数

    Itr() {}//构造方法

    public boolean hasNext() {
        return cursor != size;//游标不等于元素个数,说明还有元素。
    }

    @SuppressWarnings("unchecked")
    //下面有图解
    public E next() {
        //调用checkForComodification()会去校验expectedModCount 和modCount是否相等,不等抛异常,
        //modCount 字段作为外部类结构修改次数的记录,为子类提供快速迭代,
        //使用迭代器对集合进行遍历时,不能对集合进行修改
        checkForComodification();//修改次数是否一致
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        //实现的核心,游标+1,返回当前元素,设置最后一个索引为返回元素索引
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }
    

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();
		//调用list的remove()--游标左移--lastRet置为-1--同步修改次数
        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

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

next()方法图解:

next()方法描述

private class ListItr extends Itr implements ListIterator :

//public interface ListIterator<E> extends Iterator<E>
//可以看出ListIterator继承了Iterator,所以可以用父类的的成员变量
private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        super();
        cursor = index;
    }

    public boolean hasPrevious() {
        return cursor != 0;
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor - 1;
    }
	//与前面的next()相似
    @SuppressWarnings("unchecked")
    public E previous() {
        checkForComodification();
        int i = cursor - 1;
        if (i < 0)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i;
        return (E) elementData[lastRet = i];
    }
	//就是改变数组元素,没有操作数组长度
    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
//	 第一步,依旧是利用checkForComodification()方法对修改值modCount进行检查,判断当前数组是否发生修改。
//   第二步,获取当前的游标cursor并赋予新声明的变量i。
//   第三步,这里是极为重要的一步,就是插入动作,你也可以叫添加。它调用了ArrayList本类的add(int index, E element)方法来进行插入操作。第二步中的i就是插入的位置索引。通俗的说就是,在游标cursor处进行插入操作。
//   第四步,将当前的游标cursor进行加1操作。这步操作的目的是为了不影响游标cursor所代表的含义,即,指向某一个特定元素。你在这个位置新添加了一个元素,那么我当初游标cursor如果还在这个位置的话,是不是就指向了这个新元素?但是其本身是要指向原先你本身cursor指向的那个旧元素,怎么办呢?你就只能进行移位操作,也就是加1了。
//   第五步,我们把这个上一项元素指向lastRet初始化为-1。这种操作无非就是不让你进行set(E e)操作,抑或是不让你进行remove()操作。为什么呢?因为其实现的方法注释是规定了如此:

//remove()规定:
//只有在最后一次调用next或previous之后没有调用add时,才可以执行此操作。
//set(E e)规定
//只有在最后一次调用next或previous之后既不调用remove也不调用add,才可以执行此调用。

    public void add(E e) {
        checkForComodification();

        try {
            int i = cursor;//保存当前元素的索引 ListItr(int index) { super();cursor = index }
            ArrayList.this.add(i, e);//调用add方法
            cursor = i + 1;//游标右移
            lastRet = -1;//lastRet重置为-1
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}
是不让你进行set(E e)操作,抑或是不让你进行remove()操作。为什么呢?因为其实现的方法注释是规定了如此:

//remove()规定:
//只有在最后一次调用next或previous之后没有调用add时,才可以执行此操作。
//set(E e)规定
//只有在最后一次调用next或previous之后既不调用remove也不调用add,才可以执行此调用。

    public void add(E e) {
        checkForComodification();

        try {
            int i = cursor;//保存当前元素的索引 ListItr(int index) { super();cursor = index }
            ArrayList.this.add(i, e);//调用add方法
            cursor = i + 1;//游标右移
            lastRet = -1;//lastRet重置为-1
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值