第六章 Java的集合之ArrayList源码分析

前言

从这一章开始,将会进入JAVA集合部分的分析,包含了大部分集合大家族,如常用的ArrayList、LinkedList、HashSet、HashMap、TreeSet、TreeMap等等,还有它们的迭代器,如Iterator、listIterator、Collections工具类,然后可能还有对不常用的集合做简要分析,集合之间对比分析优点、缺点。这样,才能够让我们对Java的集合部分有了全面的了解,在编码过程中能更好的知道用什么集合。

ArrayList 结构图

在这里插入图片描述
ArrayList很常用,并且它的代码并不多。按照归类主要分为图中的几个部分。

(1)属性

	// ArrayList的默认容量
	private static final int DEFAULT_CAPACITY = 10;
	
	// ArrayList 内部维持的空数组
	private static final Object[] EMPTY_ELEMENTDATA = {};
	
	// ArrayList内部维持的默认空数组
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	
	// ArrayList维持的可变数组;ArrayList的核心属性。
	transient Object[] elementData;
	
	// 元素在容器中的个数,与elementData.length有区别。
	private int size;
	
	// 容器被修改的次数
	protected transient int modCount = 0;
	
	// ArrayList内部维持的最大数组长度
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

(2)构造器

// 无参构造
ArrayList()	

// 指定容量构造	
ArrayList(int initialCapacity)

// 将指定集合构造成ArrayList
ArrayList(Collection<? extends E> c)

(3)扩容机制

ensureCapacity(int minCapacity)
ensureCapacityInternal(int minCapacity)
ensureExplicitCapacity(int minCapacity)
grow(int minCapacity)
hugeCapacity(int minCapacity)

(4)常用API

a. 增

clone()
add(E e)
add(int index, E element)
addAll(Collection<? extends E> c)
addAll(int index, Collection<? extends E> c)

b. 删

clear()
trimToSize()
remove(int index)
remove(Object o)	fastRemove(int index)
removeRange(int fromIndex, int toIndex)
removeAll(Collection<?> c)
retainAll(Collection<?> c)
batchRemove(Collection<?> c, boolean complement)

c. 改

set(int index, E element)

d. 查

size()
isEmpty()
get(int index)
indexOf(Object o)
lastIndexOf(Object o)

e . 判断

contains(Object o)

f. 转换

toArray()
toArray(T[] a)

(5)内部类

 迭代器: Itr
		 ListItr
		 
 集合: SubList

源码分析

ArrayList 是编码过程中经常使用的一个集合。它的底层维护了一个数组 elementData
所有的操作都是基于这个数组来操作的。与StringBuffer 或 StringBuilder 有点类似。底层元素是一样,其内部也有扩容机制,以及各种各样的API,不同的是ArrayList的包容性更强,可以装载任何相同的一组对象。下面重点分析它的扩容机制 和 常用的API 和迭代器,以及为什么有扩容机制,怎么实现的,迭代器是怎样工作的,有哪些功能。
ArrayList最初被定义为一种容器,因此它有很多对外的API,如上述所分类的【增】、【删】、【查】、【改】、【判断】、【装换】。下面先从分析API开始,逐渐引出扩容机制。

(1)增 与 扩容机制

clone()

克隆方法是调用的父类的本地方法,实现的浅拷贝,关于浅拷贝和深拷贝,后面专门做一篇文章进行研究。这里不再赘述。下面贴出源码:

/**
 * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
 * elements themselves are not copied.)
 *翻译:返回这个实例的浅拷贝。元素本身内容不会被拷贝
 * @return a clone of this <tt>ArrayList</tt> instance
 */
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);
    }
}

add(E e)、add(int index, E element)

这两个方法都是增加,其不同点
add(E e)方法指添加一个元素,添加位置是末尾。
add(int index, E element)方法添加一个元素,添加位置是列表中下标为index的地方。
这两个方法都会涉及到改变ArrayList内部的数据结构,使其增加一个长度,下面进入源码分析:

add(E e)

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!  //确保足够的数组长度
    elementData[size++] = e;			// 将元素添加到最后一个位置
    return true;
 }

add(int index, E element)

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

上面都用到了扩容方法ensureCapacityInternal(size + 1),我们下面开始分析一下ArrayList的扩容机制。

扩容机制

扩容机制的诞生始于元素的添加,也就是对应的add方法,只有当容量长度不够时,才会涉及到扩容。其他功能【改】、【删】、【查】、【判断】、【转换】等等都不会涉及到扩容机制。

对外API手动扩容:  ensureCapacity(int minCapacity)
自动扩容:ensureCapacityInternal(int minCapacity)

扩容判断: ensureExplicitCapacity(int minCapacity)
	     
扩容核心:  grow(int minCapacity)  //小中容量
扩容核心:  hugeCapacity(int minCapacity)  //大容量

扩容有四个方法,最后两个为扩容核心实现方法。从外到内,分析一下:
在这里插入图片描述

ensureCapacity(int minCapacity)

这个方法提供给调用者,可手动扩容。

public void ensureCapacity(int minCapacity) {

// 计算最小扩展长度,如果是用无参构造函数,当前数组为默认空数组,那么扩展长度为10。
// 如果是有参构造或者其他,则扩展长度为0.

    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // larger than default for default empty table. It's already
        // supposed to be at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {	// 手动扩容执行条件:传入参数 > 最小扩展长度时,扩容才生效
        ensureExplicitCapacity(minCapacity);
    }
}

实例演示
通过反射来查看其内部私有属性的值。

	ArrayList list = new ArrayList();
    Class c = list.getClass();
    ArrayList list2 = (ArrayList) c.newInstance();
    Field f = c.getDeclaredField("elementData");
    f.setAccessible(true);
    list2.add(1);
    list2.ensureCapacity(100);  // 手动调用扩容
    Object[] element = (Object[]) f.get(list2);
    System.out.println(list2.size());
    System.out.println(element.length);
    
    结果:1
         100

ensureCapacityInternal(int minCapacity)

这个方法是ArrayList内部调用,相当于自动扩容,当其他方法需要扩容时,会调用该方法。扩容规则在其源码中。

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {	    // 如果为无参构造的默认空数组对象时进行扩容。
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);	// 最小扩容容量为10.
    }

    ensureExplicitCapacity(minCapacity);
}

ensureExplicitCapacity(int minCapacity)
上面的方法是计算容器所需的最小容量,这个条件是判断是否触发扩容。

  // 关于modCount,ArrayList开头有一句话:
  
  A structural modification is
 * any operation that adds or deletes one or more elements, or explicitly
 * resizes the backing array; merely setting the value of an element is not
 * a structural modification.
 * 翻译: 结构修改是添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。 

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;		// 这个变量计算当前ArrayList容器结构上被修改的次数,为什么放在扩容之前呢?
    				// 假如没有执行扩容,modCount也会+1,也就说明即时容器长度不变,
    				// 但是容器内的元素长度被改变了,如【增】【删】,modCount就会加1

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)  // 触发条件:所需最小容量 > 当前容量
        grow(minCapacity);
}

grow(int minCapacity)
扩容核心实现方法:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);   // 扩容倍数为原容量的1.5倍
    if (newCapacity - minCapacity < 0)					  // 如果扩容1.5倍后还是不够当前所需最小容量
        newCapacity = minCapacity;				 		  // 新容量就扩容为当前所需最小容量
        
        /** 从上面可以看出,并不是每次扩容都为原来的1.5倍。 **/
        
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);			// 如果所需容量非常大,超过了 2^31-8,就重新计算扩容容量
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

hugeCapacity(int minCapacity)
大容量扩容计算:

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?   // 只有两种结果:2^31 ,和 2 ^ 31 - 8
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

回到【增】功能上来,主要有四个API;
add(E e)
add(int index, E element)
addAll(Collection<? extends E> c)
addAll(int index, Collection<? extends E> c)
前面两个属于单个增加,后面两个API属于多个增加。因此对于容量的增加需求是全方面的,从0到无穷大都有可能。
弄清楚了扩容机制,容器增加元素的相关方法也很简单了。上面介绍了add()方法。下面再来看下addAll()方法;

addAll(Collection<? extends E> c)
这个方法是将集合C中的所有元素,追加到当前容器的中,增加起始位置是末尾。

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

addAll(int index, Collection<? extends E> c)
这个方法作用一致,是将集合C中的所有元素,追加到当前容器的中,增加起始位置是指定的index下标开始。

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

(2)删 与 修剪

从add 方法和 扩容机制可以知道,在向容器中添加元素的时候,当容量不足时,可以使用扩容方法增加容量。那么与此相反的操作,当在删减元素时,当容量太大时,ArrayList 也提供了修剪容量的方法。因为如果容量远远大于容器内的元素数量,占有太多的内存空间,将会造成空间浪费。
下面看一下与增元素和扩容机制 相反的 删减元素 与 修剪容量。

trimToSize()    // 修剪容量至当前元素所占长度
clear()			// 清空所有元素
remove(int index)   
remove(Object o)	fastRemove(int index)
removeRange(int fromIndex, int toIndex)
removeAll(Collection<?> c)
retainAll(Collection<?> c)
batchRemove(Collection<?> c, boolean complement)

trimToSize()

public void trimToSize() {
    modCount++;   // 容量长度的修改属于结构修改,modCount会增加
    if (size < elementData.length) {   // 如果小于才进行修剪容量,修剪为当前元素占有长度
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}

clear()

public void clear() {
    modCount++;  // 元素的删减属于结构修改

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;  // 将容器中的所有变量与堆中对象解绑,对象将被交给GC处理

    size = 0;
}

remove(int index)

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;  // 返回被移除的元素
}

remove(Object o)

public boolean remove(Object o) {
// 因为本容器可以存放null对象,非空对象的判断一般是用equals方法
// 而对于null对象,它不能用equals方法,肯定是要单独判断的。
    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;
}

removeAll(Collection<?> c) 、retainAll(Collection<?> c)和 batchRemove(Collection<?> c, boolean complement)

 // removeAll(Collection<?> c)方法中是调用的batchRemove(Collection<?> c, boolean complement)
 // 因此下面详细分析batchRemove(Collection<?> c, boolean complement);
 public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}

retainAll(Collection<?> c)

 public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}

batchRemove(Collection<?> c, boolean complement)
它有两个参数,第一个是集合接口,也就是可以传入List、Set、Vector、Queue等等
第二个参数指 是否包含。
两个参数合起来,就是指保留集合C中的所有元素还是集合C中的移除所有元素,

private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    
    try {
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)
    
                elementData[w++] = elementData[r];  // 元素长度不变,将所需的元素,从第一个位置开始装填。
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
		如果c.contains()抛出异常
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
	
		 // 装填完成后,由于装填数量 <= 容器元素初始数量,所以还要删除多余的尾部部分。
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;  // 如果元素移动后还是原来集合的样子,返回false
}

(3)改

set()
元素的修改不会影响到结构改变,因此这个方法不会使modCount增加。

 public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;  // 返回被换掉的元素
}

(4)查

ArrayList对于查找功能,提供了对长度、元素、下标三个维度的查找API,其源码思路和String,比较简单,这里不再赘述。

size()
isEmpty()
get(int index)
indexOf(Object o)
lastIndexOf(Object o)

(5)判断

contains(Object o)
其内部调用的indexOf方法,比较简单。

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

(6)转换

toArray()
toArray(T[] a)   // 这个方法暂时没看懂

toArray()

public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

(7)迭代器

下面重点分析一下内部类,包含三个内部类,其中两个迭代器和一个ArrayList的子集合类subList。

 迭代器: Itr
		 ListItr
		 
 集合: SubList

在ArrayList 集合里面有两个迭代器,和一个类似子集合功能的subList的一个类。
其中Itr 迭代器是实现了的Iterator,具有向后判断和移动的功能,而ListItr是Itr的子类,实现了ListIterator,除了在父亲能力的基础上,还增加了向前判断和移动的功能。
具体如下:
Itr

hasNext()
next()
remove()

ListItr

hasNext()
next()
remove()
hasPrevious()
previous()
set()
add()

对比上述两个迭代器可以看到,ListItr在父类的基础上增加了更多的功能,以满足对ArrayList 的各种操作。
上述两个迭代器中,都维持了下面三个变量

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

expectedModCount = modCount; 意思就是当调用者需要创建一个该集合迭代器的时候,其迭代器的已经被改过的次数将会交给expectedModCount,迭代器接过了对该集合的操作权力,并且只允许自己对集合修改,不允许外部对迭代器修改(包括集合自己)。

程序员:“我有一个集合,现在将它交给你”
迭代器:“放心吧,交给我负责”

所以,当一个集合的操作被交给了集合自己的迭代器。那么集合所有的改变结构的操作都将不能使用了,涉及结构修改的对象操作比如:

add()  addAll()  remove()等等,这些操作将会引起 modCount 的值改变。但是这个值得操作权力已经交给了迭代器。

所以当在使用迭代器遍历的过程中,有外部功能在对集合进行操作时,将会抛出ConcurrentModificationException异常,意思就是“当前并行操作异常“。所以迭代器遍历时,不允许外部操作,遍历过程中,每一个更改结构的操作,它都将根据modCount数值来判断,如果发现跟期望值不一样,就将会是快速失败的。

(8)subList 类

如果迭代器的出现是为了满足集合的重复的遍历需求,可以很方便的对集合进行操作。那么SubList 类的出现就是弥补集合的范围上的不足。
就像前面分析String 类源码一样,String 内部是数组,拥有各种各样花样繁多的操作,其中包括对子串的操作。如果当一个数组足够长,但是需要的数据范围并不必去遍历整个数组时,不论是String 还是 ArrayList 都需要提供一个可以限制范围查询或者遍历的操作。
String 类提供了 subString,同样的 subList 就是弥补 ArrayList 局部操作的一个内部类。

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

这个是arrayList 集合的一个api,它将会返回一个该集合的子集合。参数int fromIndex, int toIndex 代表了从哪里截取到哪里。
这个subList 同样的和arrayList 一样继承了 AbstractList,也实现了几乎和ArrayList 一样的功能,包括迭代器。

部分源码

	private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;

    SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }

    public E set(int index, E e) {
        rangeCheck(index);
        checkForComodification();
        E oldValue = ArrayList.this.elementData(offset + index);
        ArrayList.this.elementData[offset + index] = e;
        return oldValue;
    }

    public E get(int index) {
        rangeCheck(index);
        checkForComodification();
        return ArrayList.this.elementData(offset + index);
    }
    ...
     // subList的迭代器
     public Iterator<E> iterator() {
        return listIterator();
    }

在subList 中各项功能,均是在子集合上的相应操作。它可以减少遍历的范围,提高效率。

总结

以上就是arrayList 的源码分析,有不对的地方请多多指教。上述的某些理解是个人的理解,只做学习参考使用。arrayList 是比较常见的集合之一,集合的大家族中有很多的成员。目前计划大家族的介绍做一篇分析,常用的单独做分析,不常用的归类做分析。

猜你喜欢

转载自blog.csdn.net/weixin_43901067/article/details/104757254