zoukankan      html  css  js  c++  java
  • 【Java】ArrayList 的实现原理

    一、概述

      本例使用的是JDK8.

      一上来,先来看看源码中的这一段注释,我们可以从中提取到一些关键信息:

      Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)

      从这段注释中,我们可以得知 ArrayList 是一个动态数组,实现了 List 接口以及 list 相关的所有方法,它允许所有元素的插入,包括 null。另外,ArrayList 和 Vector 除 了线程不同步之外,大致相等。

      ArrayList 使用动态数组存储数据

    二、结构图

      

    三、属性

     1 //默认容量的大小
     2 private static final int DEFAULT_CAPACITY = 10;
     3 //空数组常量
     4 private static final Object[] EMPTY_ELEMENTDATA = {};
     5 //默认的空数组常量
     6 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMEN TDATA = {};
     7 //存放元素的数组,从这可以发现 ArrayList 的底层实现就是一个 Object数组
     8 transient Object[] elementData;
     9 //数组中包含的元素个数 private int size;
    10 //数组的最大上限
    11 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

      ArrayList 的属性非常少,就只有这些。其中最重要的莫过于 elementData 了,ArrayList 所有的方法都是建立在 elementData 之上。接下来,我们就来看一下一些主要的方法 吧。

    四、方法

    1、构造方法

      ArrayList 提供了三种方式的构造器,可以构造一个大小为 0 的空数组的空列表、构造 一个指定初始容量的空列表以及构造一个包含指定 collection 的元素的列表,这些元素按照 该 collection 的迭代器返回它们的顺序排列的。

     1 // 构造一个大小为 0 的空数组的空列表
     2 public ArrayList() {
     3     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
     4 }
     5 
     6 
     7 // 构造 一个指定初始容量的空列表
     8 public ArrayList(int initialCapacity) {
     9     if (initialCapacity > 0) {
    10         this.elementData = new Object[initialCapacity];
    11     } else if (initialCapacity == 0) {
    12         this.elementData = EMPTY_ELEMENTDATA;
    13     } else {
    14         throw new IllegalArgumentException("Illegal Capacity: "+
    15                                            initialCapacity);
    16     }
    17 }
    18 
    19 
    20 // 构造一个包含指定 collection 的元素的列表
    21 public ArrayList(Collection<? extends E> c) {
    22     elementData = c.toArray();
    23     if ((size = elementData.length) != 0) {
    24         // c.toArray might (incorrectly) not return Object[] (see 6260652)
    25         if (elementData.getClass() != Object[].class)
    26             elementData = Arrays.copyOf(elementData, size, Object[].class);
    27     } else {
    28         // replace with empty array.
    29         this.elementData = EMPTY_ELEMENTDATA;
    30     }
    31 }  

       从构造方法中我们可以看见,默认情况下,elementData 是一个大小为 0 的空数组,

      当我们指定了初始大小的时候,elementData 的初始大小就变成了我们所指定的初始 大小了。

    2、存储方法

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

      下面我们一一介绍:

     1 // 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。
     2 public E set(int index, E element) {
     3     rangeCheck(index);
     4 
     5     E oldValue = elementData(index);
     6     elementData[index] = element;
     7     return oldValue;
     8 }
     9 
    10 
    11 // 将指定的元素添加到此列表的尾部。
    12 public boolean add(E e) {
    13     // 如果数组长度不足,将进行扩容。
    14     ensureCapacityInternal(size + 1);  // Increments modCount!!
    15     elementData[size++] = e;
    16     return true;
    17 }
    18 
    19 
    20 // 将指定的元素插入此列表中的指定位置。
    21 // 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加 1)。
    22 public void add(int index, E element) {
    23     rangeCheckForAdd(index);
    24 
    25     // 如果数组长度不足,将进行扩容。
    26     ensureCapacityInternal(size + 1);  // Increments modCount!!
    27     // 将 elementData中从Index位置开始、长度为size-index的元素,
    28     // 拷贝到从下标为 index+1 位置开始的新的 elementData 数组中。
    29     // 调用一个 native 的复制方法,把 index 位置开始的元素都往后挪一位
    30     System.arraycopy(elementData, index, elementData, index + 1, size - index);
    31     elementData[index] = element;
    32     size++;
    33 }
    34 
    35 
    36 // 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此 列表的尾部。
    37 public boolean addAll(Collection<? extends E> c) {
    38     Object[] a = c.toArray();
    39     int numNew = a.length;
    40     ensureCapacityInternal(size + numNew);  // Increments modCount
    41     System.arraycopy(a, 0, elementData, size, numNew);
    42     size += numNew;
    43     return numNew != 0;
    44 }
    45 
    46 // 从指定的位置开始,将指定collection中的所有元素插入到此列表中。
    47 public boolean addAll(int index, Collection<? extends E> c) {
    48     rangeCheckForAdd(index);
    49 
    50     Object[] a = c.toArray();
    51     int numNew = a.length;
    52     ensureCapacityInternal(size + numNew);  // Increments modCount
    53 
    54     int numMoved = size - index;
    55     if (numMoved > 0)
    56         System.arraycopy(elementData, index, elementData, index + numNew,
    57                          numMoved);
    58 
    59     System.arraycopy(a, 0, elementData, index, numNew);
    60     size += numNew;
    61     return numNew != 0;
    62 }
    63 
    64 // 检查范围
    65 private void rangeCheckForAdd(int index) {
    66     if (index > size || index < 0)
    67         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    68 }   

       ArrayList 的 add 方法也很好理解,在插入元素之前,它会先检查是否需要扩容,然 后再把元素添加到数组中最后一个元素的后面。在 ensureCapacityInternal 方法中, 我们可以看见,如果当 elementData 为空数组时,它会使用默认的大小去扩容。所以 说,通过无参构造方法来创建 ArrayList 时,它的大小其实是为 0 的,只有在使用到 的时候,才会通过 grow 方法去创建一个大小为 10 的数组。

      第一个 add 方法的复杂度为 O(1),虽然有时候会涉及到扩容的操作,但是扩容的次 数是非常少的,所以这一部分的时间可以忽略不计。如果使用的是带指定下标的 add 方法,则复杂度为 O(n),因为涉及到对数组中元素的移动,这一操作是非常耗时的。

    3、调整数组容量

      从上面介绍的向 ArrayList 中存储元素的代码中,我们看到,每当向数组中添加元素时, 都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容, 以满足添加数据的需求。数组扩容通过一个公开的方法 ensureCapacity(int minCapacity)来 实现。在实际添加大量元素前,我也可以使用 ensureCapacity 来手动增加 ArrayList 实例的 容量,以减少递增式再分配的数量。

     1 // 公共的扩容方法
     2 public void ensureCapacity(int minCapacity) {
     3     int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
     4         // any size if not default element table
     5         ? 0
     6         // larger than default for default empty table. It's already
     7         // supposed to be at default size.
     8         : DEFAULT_CAPACITY;
     9 
    10     if (minCapacity > minExpand) {
    11         ensureExplicitCapacity(minCapacity);
    12     }
    13 }
    14 
    15 
    16 // 确保数组容量,容量不够进行扩容
    17 private void ensureCapacityInternal(int minCapacity) {
    18     ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    19 }
    20 
    21 // 计算最小容量
    22 private static int calculateCapacity(Object[] elementData, int minCapacity) {
    23     if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    24         return Math.max(DEFAULT_CAPACITY, minCapacity);
    25     }
    26     return minCapacity;
    27 }
    28 
    29 // 扩展数组容量
    30 private void ensureExplicitCapacity(int minCapacity) {
    31     modCount++;
    32 
    33     // overflow-conscious code
    34     if (minCapacity - elementData.length > 0)
    35         grow(minCapacity);
    36 }
    37 
    38 // 扩容操作
    39 private void grow(int minCapacity) {
    40     // overflow-conscious code
    41     int oldCapacity = elementData.length;
    42     int newCapacity = oldCapacity + (oldCapacity >> 1);
    43     if (newCapacity - minCapacity < 0)
    44         newCapacity = minCapacity;
    45     if (newCapacity - MAX_ARRAY_SIZE > 0)
    46         newCapacity = hugeCapacity(minCapacity);
    47     // minCapacity is usually close to size, so this is a win:
    48     elementData = Arrays.copyOf(elementData, newCapacity);
    49 } 

      从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的 数组中,

      每次数组容量的增长大约是其原容量的 1.5 倍

      这种操作的代价是很高的,因此在 实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时, 要在构造 ArrayList 实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求, 通过调用 ensureCapacity 方法来手动增加 ArrayList 实例的容量。

      ArrayList 还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小 的功能。它可以通过 trimToSize 方法来实现。代码如下:

    1 public void trimToSize() {
    2     modCount++;
    3     if (size < elementData.length) {
    4         elementData = (size == 0)
    5           ? EMPTY_ELEMENTDATA
    6           : Arrays.copyOf(elementData, size);
    7     }
    8 }

    4、读取方法

     1 // 返回此列表中指定位置上的元素。
     2 public E get(int index) {
     3     rangeCheck(index);
     4 
     5     return elementData(index);
     6 }
     7 
     8 private void rangeCheck(int index) {
     9     if (index >= size)
    10         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    11 } 

    5、删除方法

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

     1 // 移除此列表中指定位置上的元素
     2 public E remove(int index) {
     3     rangeCheck(index);
     4 
     5     modCount++;
     6     E oldValue = elementData(index);
     7 
     8     int numMoved = size - index - 1;
     9     if (numMoved > 0)
    10         System.arraycopy(elementData, index+1, elementData, index,
    11                          numMoved);
    12     elementData[--size] = null; // clear to let GC do its work
    13 
    14     return oldValue;
    15 }
    16 
    17 
    18 
    19 // 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。
    20 public boolean remove(Object o) {
    21     if (o == null) {
    22         for (int index = 0; index < size; index++)
    23             if (elementData[index] == null) {
    24                 fastRemove(index);
    25                 return true;
    26             }
    27     } else {
    28         for (int index = 0; index < size; index++)
    29             if (o.equals(elementData[index])) {
    30                 fastRemove(index);
    31                 return true;
    32             }
    33     }
    34     return false;
    35 }

      注意:从数组中移除元素的操作,也会导致被移除的元素以后的所有元素的向左移动一个 位置。

    五、ArrayList源码分析 

      

    六、总结

      1、ArrayList 创建时的大小为 0;当加入第一个元素时,进行第一次扩容时,默认容 量大小为 10。

      2、ArrayList 每次扩容都以当前数组大小的 1.5 倍去扩容。

      3、ArrayList 的 add、get、size 方法的复杂度都为 O(1),remove 方法的复 杂度为 O(n)。

      4、ArrayList 是非线程安全的(可以使用 Collections.synchronizedList(list) 方法解决List线程安全问题;)。 

  • 相关阅读:
    Java RunTime Environment (JRE) or Java Development Kit (JDK) must be available in order to run Eclipse. ......
    UVA 1597 Searching the Web
    UVA 1596 Bug Hunt
    UVA 230 Borrowers
    UVA 221 Urban Elevations
    UVA 814 The Letter Carrier's Rounds
    UVA 207 PGA Tour Prize Money
    UVA 1592 Database
    UVA 540 Team Queue
    UVA 12096 The SetStack Computer
  • 原文地址:https://www.cnblogs.com/h--d/p/14583879.html
Copyright © 2011-2022 走看看