集合框架之ArrayList源码分析

集合框架之ArrayList源码分析

一、继承结构


ArrayList中继承实现是这样的public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable。其中RandomAccessSerializableCloneable是三个标记接口,其实没有任何方法。

  • RandomAccessRandomAccessList 实现所使用的标记接口,用来表明其支持快速(通常是固定时间)随机访问。在需要的逻辑中用instanceof来做专门判断处理。

    public static void shuffle(List<?> list, Random rnd) {
    	int size = list.size();
        // 效率上不同,根据RandomAccess来选择一个更快的处理方式, 类似于注解
        if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {              		
        	for (int i=size; i>1; i--)
    			swap(list, i-1, rnd.nextInt(i));
                                                                     
    	} else {
    		Object arr[] = list.toArray();
    
    		// Shuffle array
            for (int i=size; i>1; i--)
    			swap(arr, i-1, rnd.nextInt(i));
    
            // Dump array back into list
            ListIterator it = list.listIterator();
            for (int i=0; i<arr.length; i++) {
                it.next();
                it.set(arr[i]);
            }
        }
    }
    
  • Serializable:标记接口,只要实现了该接口,即相当于开启可序列化操作标识。否则不能进行序列化。

  • Cloneable:标记接口,只有实现这个接口后,然后在类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常;Cloneable接口只是个合法调用 clone() 的标识(marker-interface

这些都是可以理解为注解的,注解中@interface也是一个接口,注解相当于是语法糖(说法不准,只为理解),也相当于上面说的标识接口。

二、相关方法分析

3.1 常量方法
/** 默认初始化容量为10 */
private static final int DEFAULT_CAPACITY = 10;

/** ArrayList空实例共享的一个空数组 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**  */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**  transient不可序列化, 真正存储数据的元素 */
transient Object[] elementData; 

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

/** 最大长度 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/** 被修改的次数 */
protected transient int modCount = 0;
3.2. 构造方法

有参构造函数

/**
 * 创建一个指定容量的空list
 *
 * @param  initialCapacity  初始化list的容量
 * @throws IllegalArgumentException 如果指定的初始容量为负
 */
public ArrayList(int initialCapacity) {
    // 判断initialCapacity是否大于0
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {  // 如果初始容量为0
        // 将EMPTY_ELEMENTDATA指代的空数组赋值给elementData
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        // initialCapacity小于0则抛出参数异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

无惨构造函数

/**
 * 创建一个容量为10的空list.但是默认为0,只有第一次add时候才能会将容量改为10,这也是一种延时分配容量的策略,毕竟我们很多时候不会new出ArrayList之后就立马使用。
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

使用Collection构造ArrayList

/**
 * 通过集合来构造一个List
 *
 * @param c 集合元素
 * @throws NullPointerException 如果指定的集合为null
 */
public ArrayList(Collection<? extends E> c) {
    // 将集合转成数组赋值给elementData
    elementData = c.toArray();
    // 判断元素个数是否等于0
    if ((size = elementData.length) != 0) {
        // 判断 elementData 的 class 类型是否为 Object[],不是的话则做一次转换
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 如果元素个数为0 则会使用 空数组替换 可以理解为 new ArrayList(0)
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
3.3. add方法

add方法涉及到扩容操作,具体看一下整个执行路径上涉及的方法!

先是add方法:

/**
 * 插入指定元素到list的末尾
 *
 * @param e 需要插入list的元素
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    // 添加元素的时候先将size + 1,然后开始确认容量大小是否合适,不合适就扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 添加元素,并将size加一
    elementData[size++] = e;
    // 返回添加成功
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
 * 计算list容量
 *
 * @param elementData 当前存储数据的数组
 * @param minCapacity 最小容量
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 如果是使用无参构造生成的list,这个if是true, 那么直接返回默认的容量10。
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 第一次添加元素的  DEFAULT_CAPACITY = 10 , minCapacity = 1,
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
/**
 * 确保容量足够,不够的话就需要进行扩容
 * @param  minCapacity 容量
 */
private void ensureExplicitCapacity(int minCapacity) {
    // modCount 记录操作次数加一
    modCount++;

    // minCapacity代表的时候数组下一个位置的座标,如果大于数组的长度。则说明需要扩展容量了
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
/**
 * 增加容量以确保它至少可以容纳最小容量参数指定的元素数量
 *
 * @param minCapacity 所需要的最小容量
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 右移相当于除以2, 所以此处新的容量是就容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果新的容量还是小于所需要的最小容量,那么就直接将所需要的最小容量赋值给新的容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果新的容量大于数组最大容量 
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 限制最大长度为Integer.MAX_VALUE 
        newCapacity = hugeCapacity(minCapacity);
    // 将原来的数据复制到新的空间中
    elementData = Arrays.copyOf(elementData, newCapacity);
}
3.4. 指定位置add元素
/**
 * 将指定的元素插入此列表中的指定位置
 * 将当前在该位置的元素(如果有)和任何后续元素右移(将其索引添加一个)
 *
 * @param index 指定元素要插入的索引
 * @param element 被插入的元素
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    // 检测要移除位置的下标是否合法。就是检测当前要插入的index是否大于size小于0
    rangeCheckForAdd(index);
	// 检测是否需要扩容,并将list的修改记录加一, 具体步骤看上面3.3
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将从指定位置的元素以及之后所有的元素整体右移
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 在指定位置插入元素
    elementData[index] = element;
    // list的元素数量加一
    size++;
}

来张图说明下添加的过程

3.5. remove方法

原理图:

/**
 * 移除list中指定位置的元素
 *
 * @param index 移除位置
 * @return 返回被被移除位置的元素
 * @throws IndexOutOfBoundsException 下标越界异常
 */
public E remove(int index) {
    // 检测要移除位置的下标是否合法
    rangeCheck(index);
		
    // 操作记录加一
    modCount++;
    // 获取指定位置的元素存起来
    E oldValue = elementData(index);

    // 得到要被移动的元素个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 将index位置之后的元素整体向前移动一个位置
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 设置数组最后一个位置的为空,并将该size减一
    elementData[--size] = null; // clear to let GC do its work
	// 返回被移除的元素	
    return oldValue;
}

然后在看一下另一个移除方法:

/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present.  If the list does not contain the element, it is
 * unchanged.  More formally, removes the element with the lowest index
 * <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
 * (if such an element exists).  Returns <tt>true</tt> if this list
 * contained the specified element (or equivalently, if this list
 * changed as a result of the call).
 *
 * @param o 移除指定元素从list中(指定元素存在)
 * @return <tt>true</tt> 如果当前list包含指定的元素
 */
public boolean remove(Object o) {
    // 如果指定元素是null, 则移除list中null元素
    if (o == null) {
        // 遍历所有元素,找到null的下标,调用fastRemove方法进行删除
        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;
}
/*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
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; // clear to let GC do its work
}
3.6. 查找相关的方法

indexof方法:第一个发现的

/**
 * 返回指定元素在此列表中首次出现的索引;如果此列表不包含该元素,则返回-1。
 * 如果符合 返回最低(第一个找到的)索引
 * 表达式: (o == null?get(i)== null : o.equals(get(i)))
 */
public int indexOf(Object o) {
    // 判断被查找的o不为null
    if (o == null) {
        // 如果为null,找到一个为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;
    }
    // 找不到返回 -1  
    return -1;
}

lastIndexOf:最后一个发现的

/**
 * Returns the index of the last occurrence of the specified element
 * in this list, or -1 if this list does not contain the element.
 * More formally, returns the highest index <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 */
public int lastIndexOf(Object o) {
    // 查找的元素为null
    if (o == null) {
        // 倒序查找第一个为null,返回其索引
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        // 倒序遍历查找返回下标
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    // 没找到返回-1
    return -1;
}
3.7. 迭代器

首先看一下迭代器的使用:循环中删除元素:

Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
    iterator.next();
    iterator.remove();
}

如果使用forEach循环就行删除,会出现:java.util.ConcurrentModificationExceptionfail-fast(快速失效)异常。下面介绍这个原因,先看一下ArrayList中迭代器的源码。

public Iterator<E> iterator() {return new Itr();}

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    Itr() {}

    /** 是否包含下一个 */
    public boolean hasNext() {
        return cursor != size;
    }

    /** 获取当前元素,并将游标指向下一个元素 */
    @SuppressWarnings("unchecked")
    public E next() {
        // 检测是否存在并发修改的异常,使用forEach循环删除报错的也是这个地方
        checkForComodification();
        // 记录下当前游标值
        int i = cursor;
        // i 大于list中元素的数量的时候 排除异常
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        // 判断list中元素的总数是否大于i
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        // 游标加一,指向下一个元素
        cursor = i + 1;
        // 然后通过i保存的list下标的值,将对应的数据返回
        return (E) elementData[lastRet = i];
    }

    /** 移除 */
    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            // 这边调用得是ArrayList中的remove方法
            ArrayList.this.remove(lastRet);
            // 游标回退
            cursor = lastRet;
            // 当前之复位 -1 
            lastRet = -1;
            // 同步修改记录 , 这里也是为什么我们在foreach中调用list的remove方法报错的原因,因为remove没有修改 expectedModCount,但是forEach使用的迭代器进行的,所有就报错了
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

三、相关问题

3.1. ArrayListVector的区别
  • ArrayList是线程不安全的,Vector是线程安全的
  • 扩容的时候ArrayList0.5倍,Vector1
3.2. ArrayList如何线程安全?

Collections工具类有一个synchronizedList方法,这样做意义不大。

3.3. ArrayList的存储数组使用transient进行修饰的原因

transient修饰的属性意味着不会被序列化,也就是说在序列化ArrayList的时候,不序列化elementData

因为会进行扩容,使用数组不总数满数组,全部序列化的话是浪费空间,但是这并不代表不进行序列化,只是进行部分序列化,我们可以看一下ArrayList中的writeObject

/**
 * 保存ArrayList实例到流中 (序列化).
 *
 * @serialData The length of the array backing the <tt>ArrayList</tt>
 *             instance is emitted (int), followed by all of its elements
 *             (each an <tt>Object</tt>) in the proper order.
 */
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // 写出大小作为与clone()行为兼容的容量, 反序列化的时候也需要先读取数组的大小
    s.writeInt(size);

    // 按照正确的顺序写出所有元素
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

这样做就是为了提高效率。

3.5. 关于ArrayListLinkedList的选择

当你遇到访问元素比插入或者是删除元素更加频繁的时候,你应该使用ArrayList,在ArrayList中增加或者删除某个元素,通常会调用System.arraycopy方法;在频繁的插入或者是删除元素的情况下,LinkedList的性能会更加好一点。

3.6. 关于ArrayList复制的方法

浅拷贝

clone() / addAll()  // 都是浅拷贝

ArrayList newArray = oldArray.clone();  // 使用clone方法   (浅拷贝)

ArrayList myObject = new ArrayList(myTempObject);   // 使用构造方法  (浅拷贝)

// 使用 Collections.copy,需要先指定长度(浅拷贝)
List<String> newList = new ArrayList<>(Arrays.asList(new String[list.size()]));
Collections.copy(newList, list);

深拷贝

// 序列化
try {
    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
    ObjectOutputStream out = new ObjectOutputStream(byteOut);
    out.writeObject(list);

    ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
    ObjectInputStream in = new ObjectInputStream(byteIn);
    List<String> newList = (List<String>) in.readObject();
    System.out.println(newList);
}catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}

另一种就是对List存储的数据一个个进行序列化然后装入List中,另外BeanUils中的copyProperties也是存在浅拷贝的问题的,所以最好还是通过实现Serializable进行序列化。

四、总结来说

  • ArrayList是一个动态数组,不是线程安全的,允许元素为null
  • 增导致扩容并修改modCount
  • 扩容导致数组复制,所以增删效率就很低了,但是数组天生就是对于查改很友好,效率高
  • ArrayList对于Vector来说,所有的ApI都是未加锁,所以线程不安全,而Vector加了sychronized所以他是线程安全的,以及Vector扩容时,容量翻倍,ArrayList容量增加50%
  • 虽然elementData被定义为transient,但是ArrayList被自己实现了writeObjectreadObject方法。不会将整个list的容量全都序列化了,只会对存储的元素进行序列化,避免浪费空间。
  • ArrayListclone/addAll这些方法都是浅拷贝,需要注意。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章