jdk1.8集合源码分析系列-1-ArrayList

接口继承图以及相关方法分析

先了解一下ArrayList整个接口继承图有哪些?
java

  • 看接口的时候,特别简单的咱们没必要赘述,有一些小的细节咱们会在方法上标记出来。
  • 另外父类接口和lambda方法会被省略掉。

Iterable

这里封装着迭代器通用的方法面试

public interface Iterable<T> {
    //返回一个T的迭代器
    Iterator<T> iterator();
    //其余lambda的方法这里省略掉
}
复制代码

Collection

这里封装着一些集合通用的一些方法数组

public interface Collection<E> extends Iterable<E> {
    //----查询操做----
    //返回集合长度
    int size();
    //集合为空返回true
    boolean isEmpty();
    //集合包含指定元素返回true
    boolean contains(Object o);
    /**
     * 返回包含全部集合元素的数组,这个方法用于array和collection转换,注意:
     * - 深复制出一个全新数组,不和以前的共享一个引用。
     * - 保证顺序和以前的一致。
     */
    Object[] toArray();
    /**
     * 返回包含全部集合元素的数组,注意:
     * - 若是数组a大于集合长度,多余的部分填充null
     */
    <T> T[] toArray(T[] a);

    //----修改操做----
    /**
     * 添加一个元素,注意:
     * - 这个方法有可能出现空指针,是和比较容易踩坑的方法,具体实现类若是不容许添加空元素的话。
     */
    boolean add(E e);
    boolean remove(Object o);


    //----块操做----
    //当包含c集合全部元素返回true反之false。
    boolean containsAll(Collection<?> c);
    //添加集合全部元素,注意:这个方法很容易出现空指针,c要判空
    boolean addAll(Collection<? extends E> c);
    //差集操做,移除c集合存在的元素。注意:这个方法很容易出现空指针,c要判空
    boolean removeAll(Collection<?> c);
    //交集操做,保留集合和c集合都存在的元素。注意:这个方法很容易出现空指针,c要判空
    boolean retainAll(Collection<?> c);
    //清楚集合全部元素
    void clear();

    //----比较和哈希----
    //判断两个集合是否相等
    boolean equals(Object o);
    //得到hashCode
    int hashCode();
}
复制代码

T[] toArray(T[] a);这个方法,咱们作一个小测试:安全

代码以下: bash

测试结果:数据结构

List

这里封装着一些list通用的一些操做并发

public interface List<E> extends Collection<E> {  
    //在指定位置插入c集合全部元素
    boolean addAll(int index, Collection<? extends E> c);  
    //获取指定位置的元素
    E get(int index);  
    //在指定位置替换元素。
    E set(int index, E element);  
    //在指定位置添加元素。
    void add(int index, E element);  
    //移除指定位置的元素。
    E remove(int index);  
    //获取元素的第一次找到的位置下标,没有返回-1。
    int indexOf(Object o);  
    //获取元素的最后一次找到的位置下标,没有返回-1。
    int lastIndexOf(Object o);  
    //返回list迭代器
    ListIterator<E> listIterator();  
    //返回index及之后元素的list迭代器
    ListIterator<E> listIterator(int index);  
    //返回从fromIndex到toIndex的视图
    List<E> subList(int fromIndex, int toIndex);  
}  
复制代码

Cloneable

这是一个标记接口,实现它表明你是否支持深复制。深复制和浅复制这里不赘述,感兴趣的同窗能够搜索相关资料。咱们能够经过调用其clone方法获取一个深复制的实例。dom

public interface Cloneable {
}
复制代码

RandomAccess

这也是一个标记接口,实现它标记你是否支持快速随机访问List中的元素。源码分析

public interface RandomAccess {
}
复制代码

AbstractCollection

这里面主要把Collection接口中里面一些通用的方法给实现了。测试

这里有一个隐含约定俗称的东西,咱们本身在作设计的时候AbstractXXX就表明对XXX作了一些通用逻辑的实现,不须要交给一堆的子类重复实现一遍。

这里有一个小细节,最大数组长度:MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 之因此要-8是考虑到了一些虚拟机实现中在数组保留一下头部信息。

AbstractList

这里面主要把List接口中里面一些通用的方法给实现了。

在Ideal里面观察AbstractList能够发现增长了一个fieidmodCount,它表明着“结构性修改”的次数。但你使用迭代器(inerator或者listIterator)时,迭代器会保存modCount,遍历每一个元素时都会校验一次迭代器中modCount和List中modCount中是否一致,一旦不一致,就抛出ConcurrentModificationException异常。这就是fast-fail的策略。

ArrayList源码分析

了解完接口了,下面咱们来开始了解ArrayList的代码。

ArrayList数据结构:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 默认初始化大小。具体怎么初始化待会讲
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空的数组实例,当须要返回空的数组实例时,都会返回这个实例。
     * 平时写代码也是鼓励能复用的实例提早声明,而后共享。减小无用new的次数。
     * 构造方法和trimSize都使用到了这个实例。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 也是一个空的数组,用于默认大小的空实例。
     * 可是它和EMPTY_ELEMENTDATA用途是不同的,它是用于肯定何时添加了第一个元素。
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存放具体元素的地方。
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * 用于保存集合的长度,这样在进行size计算,就不须要重复计算一遍,直接读取这个值。
     */
    private int size;

复制代码

ArrayList是一个典型的线性表结构,这里咱们简单的回顾一下线性表结构和经常使用操做的复杂度。

ArrayList的重要方法

初始化

//jdk1.8的构造方法再也不默认构造一个10长度的示例,而是构造一个空的。那么在何时扩容呢?答案是每一次add的时候
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//add方法实现以下
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
//而后咱们追溯到这个方法
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //注意这行判断,DEFAULTCAPACITY_EMPTY_ELEMENTDATA的做用体现出来了,用于判断何时第一次添加。
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
}

//指定大小的构造咱们就没必要说了。
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);
        }
    }

复制代码

扩容

//每次add(addAll)的时候会有一次内部容量确认的环节,代码以下:
ensureCapacityInternal(size + 1);

//咱们来看下实现,当容量不够可能致使溢出时,会调用grow方法进行扩容。
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

//grow方法
private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //新容量扩大到原容量的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);
    }

复制代码

边界检测

ArrayList大多数读写方法都会调用边界检测逻辑,实现比较简单,看一下就好了。

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

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
复制代码

ArrayList的源码分析暂时就到这里了,主要分析了一下面试中常常会问到的地方。

作一些关于ArrayList的题

arrayList是并发安全的么?

显然不是,好比数据结构中关键性的变量连可见性的保证都没有,好比size等。更谈不上并发安全了。

arrayList扩容策略是什么?

1.8实现中,每次add类方法会作一次容量检测,每次扩容1.5倍容量,同时会作边界检测。

相关文章
相关标签/搜索