List相关类源码解析之ArrayList

List家族主要的类结构如下:

其中List 接口继承 Iterable 为标记其迭代能力, 其子类都应该实现迭代器的功能.
我们平常主要使用的List实现为. ArrayList (array实现), LinkedList(链表实现), CopyOnWriteList (线程安全). Vector也是线程安全, 但由于其线程安全方式过于简单, 直接使用悲观锁synchronize来实现线程安全, 也是比较少会用到. 接下来主要来了解一下各个实现类. 同时会涉及到几个没有在类图表现到的 内部实现类.

ArrayList 和 Vector

ArrayList作为效率比较高的可变长集合, 使用率非常高.

一, 构造器:

           /**
    		* 构造一个具有指定初始容量的空列表。
            */
            public ArrayList(int initialCapacity) {
                super();
                if (initialCapacity < 0)
                    throw new IllegalArgumentException("Illegal Capacity: "+
                                                       initialCapacity);
                this.elementData = new Object[initialCapacity];
            }
        
            /**
             * 构造一个初始容量为 DEFAULT_CAPACITY (10) 的空列表. 
        	 * 在初始时没有长度,在第一次 add时, ensureCapacity时使用 DEFAULT_CAPACITY 来扩容长度
             */
            public ArrayList() {
                super();
                this.elementData = EMPTY_ELEMENTDATA;
            }
        	/**
             * 构造一个包含指定 collection 的元素的列表
             */							
        	public ArrayList(Collection<? extends E> c) {
                elementData = c.toArray();
                size = elementData.length;
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            }
    

二,主要属性:

 	/**
     * 没有指定初始长度时的默认长度
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 没有指定初始长度时,elementData指向的数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
	 * ArrayList的容器.即ArrayList底层实现为数组.
     */
    private transient Object[] elementData;

    /**
     * elementData已存放长度.
     */
    private int size;

**其中还有一个从AbstractList继承而来的属性要留意. **

/**
 * 外部对list结构的修改次数[如add,remove等操作]. 这个字段主要用来实现快速失败
 * 即,在进行迭代时,会检查,这个字段是否和一开始进行迭代不同. 从而确定list是否被其它线程修改.
 * 如果修改, 则抛出异常, 从而快速失败. 而不是迭代完. 产生不确定的行为. 
 */
protected transient int modCount = 0;

三, 方法

add(E e)方法简析.

 	/**
     *  将元素添加到尾部
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 在这个方法里确保elementData长度足够, 并且进行modCount++, 注意, 此处用的是size
        elementData[size++] = e;
        return true;
    }

	private void ensureCapacityInternal(int minCapacity) { //minCapacity. add成功需要的最小elementData 长度.
        if (elementData == EMPTY_ELEMENTDATA) {//如果没有指定初始长度,并且第一次add.最小长度取10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) { 
        modCount++;    //结构修改次数+1
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)    //add成功需要的最小数组长度 minCapacity与原 elementData 最大容量比较. 如果minCapacity大于最大容量, 则需要进行扩容.
            grow(minCapacity);
    }
	/**
	* 具体扩容算法. 
	*/
    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);
    }

indexOf方法解析

 	/**
	* 通过遍历的方式, 确定o的位置. 
	*/
	public int indexOf(Object o) {
        if (o == null) {   //如果o是null. 则无法通过 equals比较. 
            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;
    }

remove方法解析:

    public E remove(int index) {  //注, remove时, 不会减小,elementData已扩容的容量.需要缩小, 主动调用  trimToSize方法即可. 
        rangeCheck(index);    //检查范围是否合适

        modCount++;      //修改次数+1
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)    //大于0说明移除不是最后一个元素.如果是最后一个,不用移动. 具体remove算法. 通过数组移动来实现
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null;      // 把空出来的置为null, 可让gc及时回收
        return oldValue;
    }

其它的常用方法如,contains, indexOf等, 比较简单, 通过遍历来获取是否包含和位置, 此处不再说明. addAll以及add和remove的重载方法, 基本和上面的add和romove大同小异. 如 add(int index,E e)

 public void add(int index, E element) {
        rangeCheckForAdd(index);     //检查index是否合适

        ensureCapacityInternal(size + 1);  // 类似 add,确保elementData容量. 
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);    //位置移动
        elementData[index] = element;      //添加.
        size++;
    }

说明: transient 关键字作用, 表明, 该字段序列化时不会被序列化. 其实知道其意思(瞬时的)就基本理解了.
到这里我们基本理解了ArrayList这个类了. 但是, 我们可以看到, elementData是加了 transient的. 但是实际上, ArrayList是可以正常序列化和反序列化的. 测试便可知道. 是怎么做到的呢 ? 其实, transient只是告诉默认的序列化器. 不要将字段序列化. 但是我们在io那里学过. 有一种流是专门来写对象和读对象.并且. 序列化与反序列化时, 调用的是Object的 readObject和writeObject. 即.ArrayList 覆盖了 readObject和writeObject实现对elementData的读写.
方法如下:

/**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @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{
        int expectedModCount = modCount;
        s.defaultWriteObject();     //写入默认的字段(即没有transient修饰的字段)

        //写入长度字段,作为兼容.这个后面解释.可理解为这个是额外写入的. 
        s.writeInt(size);

        // 将所有对象写入对象流
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {  //检查是否在序列化时其它线程修改了list
            throw new ConcurrentModificationException();
        }
    }
/**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // 读取默认写入的字段(包括size)
        s.defaultReadObject();

        // 跳过 writeObject时写入的兼容size,后面解释
        s.readInt(); 

        if (size > 0) {
            
            ensureCapacityInternal(size);

            Object[] a = elementData;
            
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

简单总结如下:

  • ArrayList基于数组实现. 自动扩容. 因此使用起来比数组方便很多.
  • ArrayList也有数组的优缺点. 如, 基于索引查找速度很快.基于内容查找则需要遍历.遍历快,删除和添加较慢.
  • 带索引的方法不需要遍历数组,不带索引需要遍历数组. 如 remove(int index) 和 remove(E e).
  • contains(E e) 和 indexOf(E e) 都需要进行一次遍历.
  • remove特定位置数据和add时, 需要移动较多数据, 因此, 对于某些需要大量经常remove和add的操作不是很合适.

p.s 另外, 上面说到, readObject 和 writeObject 都处理了 size 这个非 transient字段, 这是为什么 ?
原因: 兼容旧版本的 readObject 和 writeObject. (旧版本把 elementData的length也写入了).

/**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @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();
 
        // Write out array length
        s.writeInt(elementData.length);
 
        // Write out all elements in the proper order.
        for (int i=0; i<size; i++)
            s.writeObject(elementData[i]);
 
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
 
    }
 
    /**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in size, and any hidden stuff
        s.defaultReadObject();
 
        // Read in array length and allocate array
        int arrayLength = s.readInt();
        Object[] a = elementData = new Object[arrayLength];
 
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++)
            a[i] = s.readObject();
    }

猜你喜欢

转载自blog.csdn.net/dansam/article/details/86655776
今日推荐