JCF框架源码分析系列-ArrayList(二)

一、揭开ArrayList真面目

做者将在本文详细赘述平常开发中最经常使用集合类-ArrayList,本次JCF源码分析基于JDK1.7,主要从如下几个方向分析:java

  • UML类图关系算法

  • 数据结构数组

  • 接口介绍安全

  • 经常使用、重要方法的实现数据结构

1.1 UML类图关系



                       (UML类图框架

UML关系类图,咱们能够直观的看出ArrayList的类结构,图中虚线表示实现(implements)关系,实线表示继承(extends)关系,咱们没必要在还不熟悉的状况下对这种关系进行“死记硬背”,由于在理解整个集合框架以后天然不用看都会比较清晰,辣么下面咱们将对接口或类一一介绍:dom

1.2 数据结构

1.2.1 JAVA经常使用数据结构

在介绍Collection具体的实现类或者某一种抽象以前,笔者须要对JAVA最经常使用的数据结构进行简单赘述工具

  • 线性结构:线性结构的元素在物理内存上有序/连续的分布,使其可使用下标(Index)快速访问,查找复杂度为O(1),如:数组源码分析

  • 链表结构:链表结构的元素在物理内存上无序/随机的分布,当前元素保留上一个或下一个元素的引用叫单向链表,保留上一个且下一个元素的引用叫双向链表 ,链表中第一个元素的上一个索引位置指向最后一个元素且最后一个元素的下一个索引位置指向第一个元素叫循环链表(简单理解成一根绳子头尾相连成圆),查询复杂度为O(logn),如:LinkedListLinkedHashMap性能

  • 散列结构:

以上是笔者以为最多见的一些数据结构,笔者会在后续介绍Collection不一样抽象的时候讲解集合每一种API对应的数据结构是哪种,理解这些数据结构的结构及优缺点有助于读者在根据具体的场景下选用是和数据结构的API提升性能,固然JDK里面还有一些跳表、树、图的数据结构实现,由于文章是介绍经常使用的集合类,因此就不一一介绍了,若是读者有兴趣能够自行学习

1.3 接口介绍

1.3.1 接口的语义

在这以前笔者但愿读者对接口的理解能够上升到一个逻辑层次,而不是仅仅知道实现了接口就必须实现接口的方法,好比Iterable接口是可迭代的语义,那么子类就必须是具有可迭代需求才能实现该接口,不然就属于一种错误的设计,再看看JDK中没有任何方法的Serializable、以及上图中出现的RandomAccess这些接口的出现自己就是约束着一种逻辑定义,笔者在这里称之为接口的语义

1.3.2 Iterable接口

集合顶层接口,接口的语义标志实现类是可迭代的(循环),全部须要被for(String str : strs) 语法迭代的支持都必须实现此接口(数组除外),接口方法就一个Iterator<T> iterator();此方法返回一个迭代器(Iterator接口)的实现类。

1.3.3 Collection接口

几乎是JAVA全部数据结构集合的接口(二元散列数据结构除外,如:Map),接口的语义标志实现类必须为集合,集合的解释也就是:“一堆东西”。集合里的“东西”,叫做元素(element),既然是一堆东西而咱们目的是要对这堆东西进行操做,天然Collection的接口就须要约束每一个集合都必须有如下方法:

  • size(): 集合中元素的个数

  • contains(Object element): 集合中是否包含某个元素

  • iterator(): 集合的迭代器

  • add(Object element):往集合里添加元素

  • remove(Object elemnt):从集合里面移除元素

理论上一个简单的集合接口有以上方法就能够正常工做,可是为了操做方便JCF做者Josh Bloch增长了如下几个方法的约束:

  • isEmpty():集合是否为空

  • toArray():转换成数组

  • containsAll(Collection collection):集合是否包含collection变量中的全部元素

  • addAll(Collection collection):添加collection中全部元素到当前集合

  • removeAll(Collection collection):移除全部在collection中的元素

  • retainAll(Collection collection):取两个集合的交集

  • clear():清除集合全部元素

有了以上方法的约束可让咱们在使用集合的时候操做更方便

1.3.4 List接口

介绍了经常使用数据结构,接下来咱们来看Collection的第一种集合抽象——有序List列表,List有以上两种数据结构的实现: 线性结构(ArrayList)、链表结构(LinkedList),本文咱们将赘述线性结构的实现(ArrayList),List接口在extends了Collection以后,增长了对有序列表操做的几个方法约束:

  • get(int i):获取i位置的元素

  • set(int i,Object element):替换集合中的i位置插入element元素,返回原位置的元素

  • add(int i,Object element):集合i位置插入element,i位置之后的元素向后移一位

  • remove(int i):移除集合中i位置的元素

  • indexOf(Object element):查找element并返回元素在集合的下标索引,若是没有返回-1

  • lastIndexOf(Object element):查找element元素最后一次在集合中的下标索引,若是没有返回-1

  • listIterator():建立一个集合的迭代器

  • listIterator(int i):从i位置建立一个List集合的迭代器

  • subList(int star,int end):返回当前集合的一个子集,从当前集合的start位置到end位置,注意此子集并非新new出来的对象,因此对子集的操做会影响到当前集合。

以上方法都是针对有序集合的操做,根据有序集合的特性约束一系列依靠下标索引(Index)的操做

1.3.5 RandomAccess接口

前面提到过该接口,此接口没有任何方法,存在的意义仅仅是为了表示逻辑上的一种语义——实现了该接口的子类应该具备一个特性:代表其支持快速(一般是固定复杂度)随机访问。此接口的主要目的是容许通常的算法(二分法快速排序...)更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能,因此遵循了该接口语义的实现类使用for(int i=0;i<xx;i++)会比使用Iterator迭代器速度要快,如:ArrayList根据下标索引直接访问任何位置的元素。

1.3.6 Serializable接口

此接口没有任何方法,存在的意义仅仅是为了表示逻辑上的一种语义——实现类可被序列化的。

1.3.7 Cloneable接口

此接口没有任何方法,存在的意义仅仅是为了表示逻辑上的一种语义——实现类可被克隆的。

1.4 经常使用、重要方法的实现

介绍完接口以后,咱们来看下UML类图中AbstrctCollectionAbstractListArrayList的具体实现类,在讲述过程当中笔者会省去那些非重点的方法,好比toString()isEmpty()等方法。

1.4.1 AbstractCollection抽象类

做为Collection的抽象类及几乎全部集合类的父类来讲(笔者说的是几乎,好比Map就非其子类),理应实现全部Collection接口中约束的最基本方法,但有些约束是要在具体场景下才能根据具体需求进行实现,如:add()须要把对象添加到具体的数据结构中,能够是数组(线性)、Node(链表),因此 AbstractCollection中主要实现了如下方法:

contains(Object var1)方法实现

 /**
*  方法做用:集合中是否包含var1元素
*  实现方式:使用迭代器进行迭代比对,若是先相同返回
*/
public boolean contains(Object var1) {
    Iterator var2 = this.iterator();//抽象方法,具体在子类中会实现
    if(var1 == null) {
        while(var2.hasNext()) {
            if(var2.next() == null) {
            return true;
            }
        }
    } else {
        while(var2.hasNext()) {
            if(var1.equals(var2.next())) {
            return true;
           }
        }
    }
    return false;
}

toArray()方法实现

/**
* 方法做用:集合转成数组
* 实现方式:new一个集合大小一致的数组,而后用集合迭代器迭代复制到新数组,这个地方的复制
*          使用的Arrays.copy()方法,这个方法在整个线性结构的集合里面大量存在,几乎所
*          全部的集合修改操做都使用到了它,而这个方法底层的实现来自于一个JNI方法,
*          System.arrayCopy(),因为是本地方法因此效率很是高
*/
public Object[] toArray() {
    Object[] var1 = new Object[this.size()];
    Iterator var2 = this.iterator();

    for(int var3 = 0; var3 < var1.length; ++var3) {
        if(!var2.hasNext()) {
            return Arrays.copyOf(var1, var3);//线性结构的集合中大量使用的方法
        }
        var1[var3] = var2.next();
    }

    /* finishToArray(var1,va2)方法是当new的这个数组大小不足集合大小时完成多余的赋值
     * 那么,明明是根据集合size new出来的数组为何会放不下呢,由于极可能在复制的过程
     * 中集合被修改了。
     */  
    return var2.hasNext()?finishToArray(var1, var2):var1;
}

add(E var1)方法实现,前面说过了不一样子类有不一样的实现

public boolean add(E var1) {throw new UnsupportedOperationException();}

remove(Object var1)方法实现

/**
*  方法做用:从集合中移除var1元素
*  实现方式:获取迭代器,使用迭代器中的remove删除元素
*/
 public boolean remove(Object var1) {
    Iterator var2 = this.iterator();//抽象方法,具体在子类中会实现
    if(var1 == null) {
        while(var2.hasNext()) {
            if(var2.next() == null) {
                var2.remove();//调用迭代器的remove
                return true;
            }
        }
    } else {
        while(var2.hasNext()) {
            if(var1.equals(var2.next())) {
                var2.remove();
                return true;
            }
        }
    }
    return false;
}

containsAll(Collections<?> var1);

/**
*  方法做用:集合中是否包含全部var1集合中的元素
*  实现方式:写法能够借鉴,若是是你会这样写吗?是否是代码整洁一些呢
*/
public boolean containsAll(Collection<?> var1) {
    Iterator var2 = var1.iterator();
    Object var3;
    do {
        if(!var2.hasNext()) {
            return true;
        }

        var3 = var2.next();
    } while(this.contains(var3));//调用上面的contains()方法,因此是两层循环

    return false;
}

addAll(Collection<? extends E> var1)方法实现

/**
*  方法做用:把var1集合中的全部数据添加到集合
*  实现方式:没什么好说的~~
*/
public boolean addAll(Collection<? extends E> var1) {
    boolean var2 = false;
    Iterator var3 = var1.iterator();

    while(var3.hasNext()) {
        Object var4 = var3.next();
        if(this.add(var4)) { //调用上面的add()方法,实际是子类中实现的add()方法
            var2 = true;
        }
    }

    return var2;
}

retainAll(Collection<?> var1)

/**
*  方法做用:删除当前集合在var1集合中存在的元素(集合交集,可是会把原集合数据从内存删除)
*  实现方式:代码很好懂,很少说,只要删除过一个元素 返回True,不然False
*/
public boolean retainAll(Collection<?> var1) {
    boolean var2 = false;
    Iterator var3 = this.iterator();

    while(var3.hasNext()) {
        if(!var1.contains(var3.next())) {
            var3.remove();
            var2 = true;
        }
    }

    return var2;
}

AbstractCollection总结:虽然抽象类中实现了不少方法,但实际仍是基于抽象方法之上的,具体的业务逻辑都在子类实现的抽象方法中,而AbstractCollection中实现的是与业务不要紧的公共代码,或者说最原始、最基本的实现,好比:全部查找都是使用的迭代器,实际上咱们以前介绍过RandomAccess接口的语义,因此遵循了该接口语义的实现类使用for(int i=0;i<xx;i++)会比使用Iterator迭代器速度要快,后面咱们会看到ArrayList对contains()方法进行了重写

1.4.2 AbstractList抽象类

indexOf(Object o)方法实现

/**
*  方法做用:查找元素o在集合中的位置,没有则返回-1
*  实现方式:利用迭代器进行查询
*/
public int indexOf(Object o) {
    ListIterator<E> it = listIterator();
    if (o==null) {
        while (it.hasNext())
            if (it.next()==null)
                return it.previousIndex();
    } else {
        while (it.hasNext())
            if (o.equals(it.next()))
                return it.previousIndex();
    }
    return -1;
}

iterator()方法实现,生成按从前到后顺序迭代的迭代器

public Iterator<E> iterator() {
    return new AbstractList.Itr();//生成迭代器,即下面的迭代器内部类实现代码
}

/**
 * 迭代器内部类实现,此迭代器只有next()获取下一个元素的方法,因此迭代的顺序是从前到后
 */
private class Itr implements Iterator<E> {
    int cursor; //下一个元素索引
    int lastRet; //调用next()方法返回对象的下标索引
    int expectedModCount; //模数:集合每次的修改都会+1,用来判断在迭代过程当中集合是否修改过,
                          //下面会会详细介绍
    private Itr() {
        this.cursor = 0; //初始化迭代器的时候下一个元素索引从0开始
        this.lastRet = -1;//默认-1
        this.expectedModCount = AbstractList.this.modCount;//赋值为集合的模数
    }

    public boolean hasNext() {
        //若是当前索引 != 集合大小 说名还有下一个
        return this.cursor != AbstractList.this.size();
    }

    public E next() {
        //获取下一个的时候,校验迭代器模数是否==集合模数,若是不等于则证实集合在迭代器
        //生成以后被修改过
        this.checkForComodification();
        try {
            int var1 = this.cursor;
            Object var2 = AbstractList.this.get(var1);
            this.lastRet = var1;//var2对象的下标索引
            this.cursor = var1 + 1; //这部分代码很好理解,返回下一个 当前索引+1
            return var2;
        } catch (IndexOutOfBoundsException var3) {
            this.checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public void remove() {
        if(this.lastRet < 0) {
            throw new IllegalStateException();
        } else {
            //校验迭代器模数是否==集合模数,若是不等于就说明集合在迭代器生成以后被修改过
            this.checkForComodification();

            try {
                AbstractList.this.remove(this.lastRet);//remove当前index的元素
                if(this.lastRet < this.cursor) {
                    --this.cursor; //删除成功后,下一个元素为-1
                }
                //重置-1
                this.lastRet = -1;
                //由于作了修改集合大小的操做,因此从新给模数赋值
                this.expectedModCount = AbstractList.this.modCount;
            } catch (IndexOutOfBoundsException var2) {
                throw new ConcurrentModificationException();
            }
        }
    }

    /**
     * 校验迭代器模数是否==集合模数,若是不等于则证实集合在迭代器生成以后被修改过
     * 若是集合被修改过,说名迭代器失效,抛出异常
     */
    final void checkForComodification() {
        if(AbstractList.this.modCount != this.expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
}

listIterator()方法实现

public ListIterator<E> listIterator(int var1) {
    //校验索引var1是否在0到集合size之内,不然IndexOutOfBoundsException
    this.rangeCheckForAdd(var1);
    return new AbstractList.ListItr(var1);
}

/**
 * 迭代器内部类实现,迭代顺序随意的迭代器,而且在迭代过程当中能够修改集合
 * 
 */
 private class ListItr extends AbstractList.Itr implements ListIterator<E> {
    ListItr(int var2) {
        super();
        this.cursor = var2;
    }

    public boolean hasPrevious() {
        return this.cursor != 0;//当下一个索引 !=0 的时候说名还有上一个
    }

    public E previous() { //返回上一个元素
        this.checkForComodification();

        try {
            int var1 = this.cursor - 1;
            Object var2 = AbstractList.this.get(var1);
            this.lastRet = this.cursor = var1;
            return var2;
        } catch (IndexOutOfBoundsException var3) {
            this.checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public int nextIndex() {
        return this.cursor;//返回下一个元素的索引
    }

    public int previousIndex() {
        return this.cursor - 1;//返回上一个元素的索引
    }

    public void set(E var1) {
        if(this.lastRet < 0) {
            throw new IllegalStateException();
        } else {
            this.checkForComodification();

            try {
                AbstractList.this.set(this.lastRet, var1);//把元素var1替换当前位置的值
                this.expectedModCount = AbstractList.this.modCount;
            } catch (IndexOutOfBoundsException var3) {
                throw new ConcurrentModificationException();
            }
        }
    }

    public void add(E var1) {
        this.checkForComodification();

        try {
            int var2 = this.cursor;
            AbstractList.this.add(var2, var1);//添加元素,而且把模数更新
            this.lastRet = -1;
            this.cursor = var2 + 1;
            this.expectedModCount = AbstractList.this.modCount;
        } catch (IndexOutOfBoundsException var3) {
            throw new ConcurrentModificationException();
        }
    }
}

AbstractList总结:主要实现了List接口中的以上3个方法,注意此处indexOf()仍然使用的是迭代器遍历的,由于List的数据结构分为线性和链表结构,下面咱们能够看到线性结构的ArrayList实现RandomAccess接口后对indexOf()进行了重写

  • Itr迭代器实现
      只能向后迭代,而且在迭代过程当中不能对集合元素进行add、set操做,但能够remove

  • ListItr迭代器实现
      1.ListIterator和Iterator都有hasNext()和next()方法,能够实现顺序向后遍历,可是ListIterator有hasPrevious()和previous()方法,能够实现逆向(顺序向前)遍历。Iterator不能够。
      2.ListIterator能够定位当前索引的位置,nextIndex()和previousIndex()能够实现。Iterator没有此功能。
      3.均可实现删除操做,可是ListIterator能够实现对象的修改,set()方法能够实现修改,add()能够实现添加,Iterator仅能遍历,不能修改。

1.4.3 ArrayList实现类

private int size;//集合里总共包含多少个元素,应该 <= elementData.length;
private transient Object[] elementData; //这行源码表示ArrayList底层为数组实现,那么既然底层是数组,怎么实现扩容的集合呢?请看下面这个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);//前面提到让读者留意的工具类
}

/* 再看看Arrays.copy()方法实现,实际上是System.arraycopy方法,这个方法是本地方法,效率会比较快 */
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));//底层System.arraycopy()实现
    return copy;
}

前面说过ArrayList会对contains()方法重写,接下来咱们详细看下

public boolean contains(Object o) {
    return indexOf(o) >= 0;//调用的indexOf()方法,咱们还记得这个方法在AbstractList中
                           //使用迭代器实现过,可是ArrayList又对其重写了
}

/* ArrayList已经使用for(int i=0;i<size;i++)来实现, 回想下前面咱们说过的
 * RandomAccess接口的语义就明白这个的用意,不记得的童鞋能够回到上面看下
 */
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;
}

get()方法实现

public E get(int index) {
    rangeCheck(index);//范围校验,不在0到size内就IndexOutOfBoundsException

    return elementData(index);//直接经过数组下标返回值}

set()方法实现

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

    E oldValue = elementData(index);//拿出index位置的元素
    elementData[index] = element;//设置index位置的元素为新elment    
    return oldValue;//返回原index位置元素
}

add()方法实现

public boolean add(E e) {
    ensureCapacityInternal(size + 1);//若是elementData数组长度不够当前size+1,就扩容
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == EMPTY_ELEMENTDATA) {//若是elementData仍是0,则取minCapacity和默认长度10两个数中的大值
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++; //由于是添加,因此模数须要加1

    if (minCapacity - elementData.length > 0)
        grow(minCapacity);//前面介绍过,若是长度不够则扩容
}

remove()方法实现

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);//本地方法copy数组,把index后的全部数组往前挪一个位置
    //最后一个位置置为空,注意看不少JDK源码中都会写到 Help GC或者下面这段话,
    //由于设置引用为null以后,GC能够检查到GC Roots 不可达,从而回收内存
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

subList(int fromIndex, int toIndex)方法实现

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);//校验fromIndex和toIndex是否合法
    return new SubList(this, 0, fromIndex, toIndex);
}

/* 产生一个当前集合从fromIndex位置到toIndex位置的子集合,
 * 这个地方你们看源码留意下子集合的产生过程?
 */
private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;//保存父集合的引用
    private final int parentOffset;//上面的fromIndex
    private final int offset;//针对父集合的偏移量,后续根据用户输入的index进行偏移
    int size;

    SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;//默认赋值为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();
        //其实就是获取父集合指定位置的元素,因此子集合并无new出新内存,这点请读者理解
        return ArrayList.this.elementData(offset + index);
    }

    public int size() {
        checkForComodification();
        return this.size;
    }

    public void add(int index, E e) {
        rangeCheckForAdd(index);
        checkForComodification();
        parent.add(parentOffset + index, e);
        this.modCount = parent.modCount;
        this.size++;
    }

    public E remove(int index) {
        rangeCheck(index);
        checkForComodification();
        E result = parent.remove(parentOffset + index);
        this.modCount = parent.modCount;
        this.size--;
        return result;
    }

    protected void removeRange(int fromIndex, int toIndex) {
        checkForComodification();
        parent.removeRange(parentOffset + fromIndex,
                           parentOffset + toIndex);
        this.modCount = parent.modCount;
        this.size -= toIndex - fromIndex;
    }

    public boolean addAll(Collection<? extends E> c) {
        return addAll(this.size, c);
    }

    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);
        int cSize = c.size();
        if (cSize==0)
            return false;

        checkForComodification();
        parent.addAll(parentOffset + index, c);
        this.modCount = parent.modCount;
        this.size += cSize;
        return true;
    }

ArrayList总结:由于ArrayList的底层是数组实现,因此它是一个线性的结构,它在内存中是一段连续的地址,因此咱们能够经过Index很快的访问元素,然而咱们看到了每add或者remove操做都会调用System.arraycopu()复制改动以后的全部元素,因此咱们说随机插入和删除操做线性结构没有链表结构效率高(这里请你们注意,后续讲到链表结构的时候会作一个对比,看看是否必定线性结构的效率比较高)

而后请你们注意的是subList()方法,上面有说过subList并不是产生一个新对象,而是经过使用下标偏移量取父类数组中的元素,也就是说子集合和父集合其实在内存中是一个集合,因此对子集合的任何修改将直接影响到父集合,咱们不少刚入门的童鞋,可能再使用中会遇到这个问题,因此在这里提出来

二、 总结

ArrayList讲到这里就已经讲完了,由于ArrayList做为咱们JCF框架源码分析系列的第二集,因此里面着重的介绍了Iterable、Iterator、Collection、AbstractCollection、List、Abstract、RandomAccess等接口或抽象类,后续文章中若有重复的就不在赘述。

转载请注明出处:http://my.oschina.net/u/926166/blog/537331

相关文章
相关标签/搜索