ArrayList 的实现原理

一、ArrayList 概述

ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数 组的大小。 每个ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是大于等于列表的大小。随着向ArrayList 中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造 ArrayList 时 指定其容量。

注意,此实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。 相对比的,Vector是线程安全的,其中涉及线程安全的方法皆被同步操作了。即使如此我们也不建议使用Vector,而是使用Collections工具类提供的synchronizedList(List list)将ArrayList转换为线程安全的。

二、ArrayList 的实现(以jdk1.8为例)

对于ArrayList 而言,它实现 List 接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。下面我们来分析 ArrayList 的源代码:

1、底层使用数组实现

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

2、构造器

ArrayList 提供了三种方式的构造器:

  1. 可以构造一个默认初始容量为 10 的空列表(在添加第一个元素的时候默认初始化容量为10,而不是再创建对象的时候就初始化容量为10,,JDK1.7创建对象的时候就会默认初始化创建容量为10 的数组)
  2. 构造一个指定初始容量的空列表
  3. 构造一个包含指定 collection 的元素的列表

这些元素按照 该 collection 的迭代器返回它们的顺序排列的。


    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    public ArrayList() {
    	// 1、可以构造一个默认初始容量为 10 的空列表
    	// (在添加第一个元素的时候默认初始化容量为10,而不是再创建对象的时候就初始化容量为10)
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
	
	private static final Object[] EMPTY_ELEMENTDATA = {};
	
	public ArrayList(int initialCapacity) {
		// 2、构造一个指定初始容量的空列表
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    public ArrayList(Collection<? extends E> c) {
    	// 3、构造一个包含指定 collection 的元素的列表
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
	

3、存储

ArrayList 提供了 set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection c)、addAll(int index, Collection c)这些添加元素的方法。

add(E e)方法详解:
	// 将指定的元素添加到此列表的尾部
    public boolean add(E e) {
    	// 确保数组的容量能够装下新添加的元素,如果不够充足则进行扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
	
	private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
	
    private static final int DEFAULT_CAPACITY = 10;
	// elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 判断elementData 是否为空
	// 如果为空则此次添加元素是第一次添加元素,返回DEFAULT_CAPACITY
	// 否则返回minCapacity
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
	
	// minCapacity - elementData.length > 0 
	// 如果增加一个元素后的容量比现有的容量大,则使用grow进行扩容
	private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
	
	// 扩容方法
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); //  >> 表示除2,即扩容1.5倍
        if (newCapacity - minCapacity < 0) // 如果扩容后的容量让仍然不够存储,则将minCapacity作为新的数组的容量(addAll()添加集合时或使用到)
            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);  // 将原来的数组中的数据拷贝到新的数组中
    }

从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长是其原容量的 1.5 倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时, 要在构造 ArrayList 实例时,就指定其容量,以避免数组扩容的发生。

4、读取

	// 返回此列表中指定位置上的元素。
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    
    E elementData(int index) {
        return (E) elementData[index];
    }

5、删除

ArrayList 提供了根据下标或者指定对象两种方式的删除功能

	// 根据数组下标进行删除
    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; // clear to let GC do its work

        return oldValue;
    }
	
	// 根据对象进行删除
    public boolean remove(Object o) {
        if (o == null) {
            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;
    }

三、总结:

3.1、ArrayList的源码分析:

3.1.1 jdk 7情况下
  •  ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
    
  •  list.add(123);//elementData[0] = new Integer(123);
    
  •  ...
    
  •  list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
    
  •  默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
    
  •   如果是list.addAll(Collection)添加元素,如果容量不够,像扩容1.5倍,
      如果仍然不够,则将原来数组的长度与新添加的集合的长度相加作为新的集合的长度
    
  •  结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
    
3.2.2 jdk 8中ArrayList的变化:
  •  ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没创建长度为10的数组
    
  •  list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
    
  •  ...
    
  •  后续的添加和扩容操作与jdk 7 无异。
    
3.2.3 小结:

jdk7中的ArrayList的对象的创建类似于单例的饿汉式, 而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

3.2、LinkedList的源码分析:

  •  LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
    
  •  list.add(123);//将123封装到Node中,创建了Node对象。
    
  •  其中,Node定义,体现了LinkedList的双向链表的说法
    
  •  private static class Node {
          E item;
          Node next;
          Node prev;
    
          Node(Node prev, E element, Node next) {
          this.item = element;
          this.next = next;
          this.prev = prev;
          }
      }
    

3.3、Vector的源码分析:

jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。

你可能感兴趣的:(原理刨析,链表,java)