美文网首页
Arraylist(JDK1.8)

Arraylist(JDK1.8)

作者: 小王ovo | 来源:发表于2018-08-29 21:32 被阅读0次

    1. 数据结构

    数组(没错就是简单的数组)

    2.类的继承关系

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

    (ArrayList继承AbstractList抽象父类,实现了List接口(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)。)

    3.类的属性

    private static final long serialVersionUID = 8683452581122892189L;//版本号

    private static  final int DEFAULT_CAPACITY = 10;//默认大10

    private static final Object[] EMPTY_ELEMENTDATA = {};//空对象数组

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//元素数组

    transient Object[] elementData//实际元素大小默认为零

    private int size;//ArrayList的大小(它包含的元素的数量)。

    4.构造函数

    1. ArrayList(Collectionextends <? extends E> c) {

    //参数为集合的构造函数   

    elementData = c.toArray();

    //转化为数组

    if ((size elementData.length) != 0) {

    //判断参数是否为空        

    if (elementData.getClass() != Object[].class)      //判断是否成功转化为object数组

    elementData = Arrays.copyOf(elementDatasize, Object[].class);//不为object数组就进行复制    } else {

    this.elementData EMPTY_ELEMENTDATA;

    //设置元素数组为空。    }}

     

     

     

    2.public ArrayList(int initialCapacity) {

    //参数为int的构造函数

    if (initialCapacity > 0) {//初始容量大于0

    this.elementData=new Object[initialCapacity];    }//初始化元素数组

    else if (initialCapacity == 0)//初始容量为0 {this.elementData EMPTY_ELEMENTDATA;    }

    //设置为空对象数组

    else {

    throw new IllegalArgumentException("Illegal Capacity: "+                                           initialCapacity);    }}//初始容量小于0,抛出异常.

    3.public ArrayList() {

            //无参构造函数,设置元素数组为空 

    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

        }

    5.重要函数

    5.1 add函数

    public boolean add(E e) {

    ensureCapacityInternal(size + 1);          elementData[size++] = e;

            return true;

        }

    //内容很简单就是添加元素,我们重点看一下ensureCapacityInternal()

    private void ensureCapacityInternal(int minCapacity)

    {        ensureExplicitCapacity

    (calculateCapacity(elementData, minCapacity));

    }

    private void ensureExplicitCapacity(int minCapacity) {

            modCount++;//结构性修改加1

            // overflow-conscious code

            if (minCapacity - elementData.length > 0)

                grow(minCapacity);

        }

    private static int calculateCapacity(Object[] elementData, int minCapacity) {

            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

    //判断elementData是否为空

                return Math.max(DEFAULT_CAPACITY, minCapacity);

            }//返回较大值

            return minCapacity;

        }

    这几个函数有两个作用

    1.赋予elementData合适的大小

    2.扩容

    此时我们看一下扩容函数grow()

    private void grow(int minCapacity) {

            // overflow-conscious code

            int oldCapacity = elementData.length;

             //旧容量大小

            int newCapacity = oldCapacity + (oldCapacity >> 1);

        //新容量大小为旧容量大小的1.5倍

            if (newCapacity - minCapacity < 0)

                newCapacity = minCapacity;

    //新容量小于参数指定容量,修改新容量

    if (newCapacity - MAX_ARRAY_SIZE > 0)

                newCapacity=hugeCapacity(minCapacity);

    //新容量大于最大容量

    elementData=Arrays.copyOf(elementData, newCapacity);

    }

    //拷贝扩容

    正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。当我们调用add方法时,实际上的函数调用如下

    也就是说我们调用add函数,实际调用的函数为

    程序调用add,实际上还会进行一系列调用,可能会调用到grow,grow可能会调用hugeCapacity。

    接下来用一个例子说明一下

    List lists = new ArrayList();

    lists.add(9);

    我们可以看到,在add方法之前开始elementData = {};调用add方法时会继续调用,直至grow,最后elementData的大小变为10,之后再返回到add函数,把9放在elementData[0]中.

    5.2 set函数

      public E set(int index, E element) {

            rangeCheck(index);

    //检查索引是否合法(有没有超出范围)

            E oldValue = elementData(index);

        //旧值

            elementData[index] = element;

        //新值

            return oldValue;

    }

    [if !supportLists]5.2 [endif]indexOf函数

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

    5.3 get函数

    public E get(int index) {

            rangeCheck(index);//判断索引是否合法

            return elementData(index);

    }

    E elementData(int index) {

            return (E) elementData[index];

    }//返回值都经过了向下转型onject-E。

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

            elementData[--size] = null; 赋值为空,有利于进行GC

            return oldValue;

        }

    remove函数用户移除指定下标的元素,此时会把指定下标到数组末尾的元素向前移动一个单位,并且会把数组最后一个元素设置为null,这样是为了方便之后将整个数组不被使用时,会被GC,可以作为小的技巧使用。

    6.fail-fast的原理

    1. fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出ConcurrentModificationException异常。fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测bug。

    具体由Iterator的内部类Itr实现

    public Iterator iterator() {

    return new Itr();

    }

    private class Itr implements Iterator {

            int cursor;       // index of next element to return

            int lastRet = -1; // index of last element returned; -1 if no such

            int expectedModCount = modCount;

     

            Itr() {}

    ..............

    }

    cursor是指集合遍历过程中的即将遍历的元素的索引,lastRet是cursor -1,默认为-1,即不存在上一个时,为-1,它主要用于记录刚刚遍历过的元素的索引。expectedModCount这个就是fail-fast判断的关键变量了,它初始值就为ArrayList中的modCount。(modCount是抽象类AbstractList中的变量,默认为0,而ArrayList 继承了AbstractList ,所以也有这个变量,modCount用于记录集合操作过程中作的修改次数,与size还是有区别的,并不一定等于size)迭代器迭代结束的标志就是hasNext()返回false,

    public boolean hasNext() {

    return cursor !=size;

    }

    而该方法就是用cursor游标和size(集合中的元素数目)进行对比,当cursor等于size时,表示已经遍历完成。在ArrayList进行add,remove,clear等涉及到修改集合中的元素个数的操作时,modCount就会发生改变(modCount ++),所以当另一个线程(并发修改)或者同一个线程遍历过程中,调用相关方法使集合的个数发生改变,就会使modCount发生变化,这样在checkForComodification方法中就会抛出ConcurrentModificationException异常。

    6.1解决方案

    1.使用Arraylist迭代器的remove(其中没有modCount ++)。

    public void remove() {

    if (lastRet <0)

    throw new IllegalStateException();

    checkForComodification();

    try {

    ArrayList.this.remove(lastRet);

    cursor =lastRet;

    lastRet = -1;

    expectedModCount =modCount;

    }catch (IndexOutOfBoundsException ex) {

    throw new ConcurrentModificationException();

    }

    }

    2.使用java并发包(java.util.concurrent)中的类来代替。

    相关文章

      网友评论

          本文标题:Arraylist(JDK1.8)

          本文链接:https://www.haomeiwen.com/subject/jhqlwftx.html