Java进阶--深入理解ArrayList实现原理

ArrayList源码分析主要由五部分组成,一是继承和实现类,二是基本属性,三是构造方法,四是主要方法,五是分析与总结。

一、 ArrayList概述:

ArrayList特点:是基于数组实现的动态数组,其容量能自动增长,元素有顺序、可重复 、查询快、增删慢、线程不安全,内部的元素可以直接通过get与set方法进行访问。

ArrayList继承了AbstractList,实现了RandomAccess、Cloneable和Serializable接口!

public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable

首先接口意义在于规范设计,但接口是没有具体实现的(1.8版本之后的静态方法除外),所有继承它的类都需要一个个实现它的所有方法。
但是呢接口可能有些方法具有一定的通用性,也就是说不需要所有实现类都单独实现(实现了代码也是重复的),有一个类实现这几个方法,大家都去继承它好了,剩下的接口大家各自实现,这就是模板类的意义。为什么要把这个模板类定义成抽象类呢?因为继承接口的实现类必须要实现接口的所有方法,而模板类仅仅是为了实现一些通用方法不需要实现所有接口方法(总不能为了提供这个模板,稀里糊涂的把所有接口方法都实现了吧,没有必要不说,还和可能产生不必要的使用。),这时候抽象类就符合这个需求了。
抽象类实现了通用的接口,剩下的接口谁继承我谁实现,而且抽象类本身又不能实例化,不会产生没有实现的接口滥用。既实现了代码的最大化复用,又实现了代码的最大化精简。

(1)继承AbstractList,把模板类定义成抽象类,抽象类本身不能实例化,继承接口的实现类必须要实现接口的所有方法,而模板类仅仅是为了实现一些通用方法不需要实现所有接口方法,不会产生没有实现的接口滥用。实现了代码的最大化复用和代码的最大化精简。
AbstractList又继承了AbstractCollection实现了List接口,List提供了相关的添加、删除、修改、遍历等功能!

public abstract class AbstractList extends AbstractCollection implements List {
    protected AbstractList() {
    }

(2)实现了List接口,表明ArrayList是一个有序、可重复的数组,提供了相关的添加、删除、修改、遍历等功能!

(3)实现RandomAccess接口,提供了随机访问功能,是一个标记接口,实际上就是通过下标序号进行快速访问,主要目的是使算法能够在随机和顺序访问的list中表现的更加高效。
Collections下的binarySearch方法的源码:

public static int binarySearch(List> list, T key) {
    if (list instanceof RandomAccess || list.size()

indexedBinarySearch是直接通过get访问数据
iteratorBinarySearch是通过ListIterator查找响应的元素

实现RandomAccess接口的的List可以通过简单的for循环来访问数据比使用iterator访问来的高效快速。

(4)实现了Cloneable接口,表明ArrayList支持克隆,即可以重写Object.clone方法,否则在重写clone方法时,报CloneNotSupportedException
(5)实现了Serializable接口可以被序列化与反序列化。

二、ArrayList的继承关系

java.lang.Object 
    java.util.AbstractCollection 
        java.util.AbstractList 
            java.util.ArrayList 
 
All Implemented Interfaces: 
Serializable, Cloneable, Iterable, Collection, List, RandomAccess 
Direct Known Subclasses: 
AttributeList, RoleList, RoleUnresolvedList

三、ArrayList方法(API)

// Collection中定义的API
boolean             add(E object)
boolean             addAll(Collection collection)
void                clear()
boolean             contains(Object object)
boolean             containsAll(Collection collection)
boolean             equals(Object object)
int                 hashCode()
boolean             isEmpty()
Iterator         iterator()
boolean             remove(Object object)
boolean             removeAll(Collection collection)
boolean             retainAll(Collection collection)
int                 size()
 T[]             toArray(T[] array)
Object[]            toArray()
// AbstractCollection中定义的API
void                add(int location, E object)
boolean             addAll(int location, Collection collection)
E                   get(int location)
int                 indexOf(Object object)
int                 lastIndexOf(Object object)
ListIterator     listIterator(int location)
ListIterator     listIterator()
E                   remove(int location)
E                   set(int location, E object)
List             subList(int start, int end)
// ArrayList新增的API
Object               clone()
void                 ensureCapacity(int minimumCapacity)
void                 trimToSize()
void                 removeRange(int fromIndex, int toIndex)

四、构造方法(有3个)

(1)无参构造,初始化长度为0的数组

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

(2)传入int类型的值,初始化长度为自定义的数组

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

(3)对传入的集合元素进行复制,构造一个包含指定元素的列表

public ArrayList(Collection 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;
    }
}

五、 主要方法解析

  1. add(E e),在元素列表的末尾插入指定的元素,返回true或者抛出异常,无其他返回值
// 在列表的末尾添加指定元素
public boolean add(E e) {
    // 确定内部数组的容量
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 同时将指定元素赋值到数值的对应下标
    elementData[size++] = e;
    return true;
} 

private void ensureCapacityInternal(int minCapacity) {
    // DEFAULT_CAPACITY ,默认数组边界长度10,
    // 每一次添加元素之后,用默认数组边界长度10和添加元素之后的列表长度比较,取最大值
    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);
    // 真实的Array[],可以看到是原来的列表,长度为最新扩容后的
    elementData = Arrays.copyOf(elementData, newCapacity);
}

2. 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++;
}
  1. addAll(Collection c),将指定集合追加到列表的末尾,返回值为true或者false,还可能抛出异常
// 在列表的结尾追加一个集合
public boolean addAll(Collection 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;
}
  1. addAll(int index, Collection c),在指定位置追加指定元素列表,返回值为true或者false,还可能抛出异常
public boolean addAll(int index, Collection 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;
}
  1. remove(int index),移除指定下标对应的列表元素,返回值为指定下标对应的列表元素
/**
     *按下标移除元素
     */
    public E remove(int index) {
        //先判断index是否大于等于size,如果大于等于将报异常
        rangeCheck(index);
 
        modCount++;
        //防止数组index下标位置所指向的内存在移动元素的时候被占用
        E oldValue = elementData(index);
        //需要在elementData数组中移动元素的长度
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //将elementData从下标index+1开始的元素到,长度为numMoved,移除到elementData的下标为index开始的地方
            //这样就能将elementData中下标为index的元素覆盖掉
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        elementData[--size] = null; // Let gc do its work
 
        return oldValue;
    }
  1. remove(Object o),移除列表中的指定元素,返回值为true或者false,也可抛出异常
public boolean remove(Object o) {
    if (o == null) {
        // 如果移除的指定元素为null,先找到列表中值为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);
    // 通知垃圾回收                    
    elementData[--size] = null; 
}
  1. removeAll(Collection c),移除指定的集合元素
public boolean removeAll(Collection c) {
    // 采用Objects工具类,如果指定集合为null,则抛出空指针异常
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}

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.
        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;
}
  1. removeIf(Predicate filter),根据指定的筛选条件删除指定的列表元素,这是Jdk1.8新增的方法
@Override
public boolean removeIf(Predicate filter) {
    // Objects工具类判定null操作
    Objects.requireNonNull(filter);
    
    int removeCount = 0;
    // 装载int类型的小型数组,可以理解为装载int的列表
    final BitSet removeSet = new BitSet(size);
    // 因为后面的遍历列表元素,可能有多线程同时访问的情况,固做此标记
    final int expectedModCount = modCount;
    final int size = this.size;
    // 遍历列表元素找到符合筛选条件的下标,然后存储到BitSet中
    for (int i = 0; modCount == expectedModCount && i < size; i++) {
        @SuppressWarnings("unchecked") final E element = (E) elementData[i];
        if (filter.test(element)) {
            removeSet.set(i);
            removeCount++;
        }
    }
    // 上面如果有多线程同时访问,则此处可能会报异常
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }

    // shift surviving elements left over the spaces left by removed elements
    final boolean anyToRemove = removeCount > 0;
    // 如果符合筛选提交的元素个数不为0,则进行删除操作
    if (anyToRemove) {
        // 移除符合筛选条件的元素后,列表的长度
        final int newSize = size - removeCount;
        
        for (int i = 0, j = 0; (i < size) && (j < newSize); i++, j++) {
            // 可以参加下面的testBitSet测试分析,它表示返回的是正序排列BitSet中没有的int类型值
            i = removeSet.nextClearBit(i);
            elementData[j] = elementData[i];
        }
        for (int k = newSize; k < size; k++) {
            elementData[k] = null;  // Let gc do its work
        }
        this.size = newSize;
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
    return anyToRemove;
}

测试BitSet类型的存储,主要测试nextClearBit方法


微信图片_20190821185923.jpg
public void testBitSet() {
    // 长度为6,对应存储元素为{0,1,2,3,4,5}
    BitSet removeSet = new BitSet(6);
    // 如下为装载的数据,浅绿色标记
    removeSet.set(1);
    removeSet.set(0);
    removeSet.set(3);
    removeSet.set(5);

    // 可采用如下方式,找到为装载的数据,即为{2,4}
    for (int i = 0; i < 6; i++) {
        System.out.println("清除前i=" + i);
        i = removeSet.nextClearBit(i);
        System.out.println("清除后i=" + i);
        System.out.println("-----------------");
    }
}

清除前i=0
清除后i=2
-----------------
清除前i=3
清除后i=4
-----------------
清除前i=5
清除后i=6
-----------------

测试removeIf(Predicate filter)方法

public void testRemoveIf() {
    ArrayList mArrayList = new ArrayList<>();
    mArrayList.add("a");
    mArrayList.add("b");
    mArrayList.add("ab");
    mArrayList.add("c");
    mArrayList.add("aa");
    mArrayList.add("d");
    System.out.println("初始时:" + mArrayList.toString());
    mArrayList.removeIf(s -> s.contains("a"));
    System.out.println("过滤完:" + mArrayList.toString());

    // 初始时:[a, b, ab, c, aa, d]
    // 过滤完:[b, c, d]
}
  1. clone(),克隆列表,是一种浅拷贝,拷贝的装载对象的内存地址
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);
    }
}
  1. set(int index, E element),用指定元素替换指定下标的值
public E set(int index, E element) {
    rangeCheck(index);

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

5、总结

1:ArrayList 本质实现方法是用数组!是非同步的!

2:初始化容量 = 10 ,最大容量不会超过 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8!

3:indexOf和lastIndexOf 查找元素,若元素不存在,则返回-1!

4:当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量=“(原始容量x3)/2 + 1”。

5:ArrayList的克隆函数,即是将全部元素克隆到一个数组中。

6:ArrayList实现java.io.Serializable的方式。当写入到输出流时,先写入“容量”,再依次写入“每一个元素”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。

7:造成ArrayList添加(add)慢的原因是什么?
当容量不够时,每次增加元素,使用System.arraycopy()方法将原来的元素拷贝到一个新的数组中,造成了资源的浪费,也因此建议在事先能确定元素数量的情况下,在使用ArrayList。

8:ArrayList的实现中大量地调用了Arrays.copyof()和System.arraycopy()方法。

9:造成ArrayList移除(remove)慢的原因是什么?
ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次根据下标插入或删除元素时,就需要调用System.arraycopy()方法将下标前数据和下标后的数据进行拼接并复制到新的数组里,因此插入、删除元素的效率低。

10:在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,ArrayList中允许元素为null。

11:ArrayList为什么能自动扩展大小,不需要手动设置大小?
ArrayList内部采用的是Array对象存储,本身该对象是需要定义长度的,所以每次添加数据的时候,都是判断是否先扩展容量,且容量有最大值限制,容量的大小是在原来基础上扩大1.5倍:公式(oldCapacity + oldCapacity >> 1)。

12:ArrayLIst查询效率高:ArrayLIst是连续存放元素的,找到第一个元素的首地址,再加上每个元素的占据的字节大小就能定位到对应的元素。

13:LinkedList插入删除效率高。因为执行插入删除操作时,只需要操作引用即可,元素不需要移动元素,他们分布在内存的不同地方,通过引用来互相关联起来。而ArrayLIst需要移动元素,故效率低。

你可能感兴趣的:(Java进阶--深入理解ArrayList实现原理)