📜  Java中 ArrayList 的内部工作

📅  最后修改于: 2022-05-13 01:55:25.583000             🧑  作者: Mango

Java中 ArrayList 的内部工作

ArrayList 是Java中可调整大小的数组实现。 ArrayList 动态增长并确保始终有空间来添加元素。 ArrayList 的后备数据结构是 Object 类的数组。 Java中的 ArrayList 类有 3 个构造函数。它有自己版本的 readObject 和 writeObject 方法。 ArrayList 中的对象数组是瞬态的。它实现了 RandomAccess、Cloneable、 Java.io.Serializable( Java中的标记接口)

宣言

ArrayList 在内部使用 Object[] Array,它是一个对象数组。删除、添加和更新元素等所有操作都发生在这个 Object[] 数组中。                               

/**
    * The array buffer into which the elements of the ArrayList are stored.
    * The capacity of the ArrayList is the length of this array buffer. Any
    * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    * will be expanded to DEFAULT_CAPACITY when the first element is added.
    */
transient Object[] elementData; // non-private to simplify nested class access    

上面的代码来自Java 8,在Java 7 中,数组被声明为私有瞬态对象,但在Java 8 中它不是私有的,因为非私有是为了简化对嵌套类(如 Itr、ListItr、SubList)的访问。

初始化

List arrayList = new ArrayList();

在调用 ArrayList 类的默认构造函数时执行下面的代码声明 ArrayList 。

在Java 7 中

public ArrayList() {
     this(10);
}

因此 Array 大小的默认容量为 10。

在Java 8 中

private static final int DEFAULT_CAPACITY = 10;\\ Default initial capacity.
  
// Shared empty array instance used for empty instances.
private static final Object[] EMPTY_ELEMENTDATA = {};

  /**
   * Shared empty array instance used for default sized empty instances. We
   * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
   * first element is added.
   */
   
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

这里,List 初始化时默认容量为 10。当第一个元素插入到 Array 列表中时,元素数据== DEFAULTCAPACITY_EMPTY_ELEMENTDATA的数组列表将扩展为 DEFAULT_CAPACITY(将第一个元素添加到 ArrayList)。

构造函数

要创建一个 ArrayList,首先需要创建 ArrayList 类的 Object.. ArrayList 在Java 8 中包含 3 种类型的构造函数

  1. ArrayList() :这个构造函数是初始化一个空的List。
  2. ArrayList(int capacity):这个构造函数我们可以将容量作为参数传递,用于由用户初始化容量。
  3. ArrayList(Collection c):在这个构造函数中,我们可以传递一个Collection c作为参数,其中一个Array列表将包含Collection c的元素。

ArrayList():此构造函数用于创建初始容量为 10 的空 ArrayList,这是默认构造函数。我们可以通过 ArrayList 类的引用名称 arr_name 对象创建一个空的 Array 列表,如下所示。

ArrayList arr_name = new ArrayList();  

以下是此构造函数的内部代码(在Java 8 中):

// Constructs an empty Arraylist with an initial capacity of ten.
    
public ArrayList() {
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

在上面的代码中,当我们将第一个元素添加到数组列表中时,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 将更改为 DEFAULT_CAPACITY。 (DEFAULT_CAPACITY =10)。

ArrayList(int capacity):此构造函数用于创建具有用户提供的初始容量的 ArrayList。如果我们想创建一个指定大小的 ArrayList,我们可以通过这个构造函数传递值。内部使用用户给定的大小创建的对象数组。例如,如果用户希望 Array 列表大小应为 7,则可以在构造函数中传递值 7,可以如下创建:

ArrayList arr = new ArrayList(7);

以下是此构造函数的内部代码(在Java 8 中):

public ArrayList(int initialCapacity) {

       if (initialCapacity > 0) {
           this.elementData = new Object[initialCapacity];  
           
       } else if (initialCapacity == 0) {
       
           this.elementData = EMPTY_ELEMENTDATA;
       } else {
       
           throw new IllegalArgumentException("Illegal Capacity: "+  initialCapacity);
       }
}

在上面的代码中,可以传递给构造函数的 size 大于 0 ( initialCapacity>0 ) 创建的对象数组将在给定的容量内。如果传递的容量等于 0( initialCapacity==0 ),则将创建一个空的 Arraylist。如果初始容量小于 0 ( initialCapacity<0 ),则会抛出 IllegalArgumentException

ArrayList(Collection c ):此构造函数用于创建一个数组列表,该列表使用传递给构造函数 (Collection c ) 的集合中的元素进行初始化。可以根据传递给构造函数的特定集合创建 ArrayList 的对象。

ArrayList arrayList = new ArrayList(new LinkedList());

以下是此构造函数的内部代码(在Java 8 中):

public ArrayList(Collection c) {
       elementData = c.toArray();
       if ((size = elementData.length) != 0) {
       
           // c.toArray might (incorrectly) not return Object[] 
           if (elementData.getClass() != Object[].class)
               elementData = Arrays.copyOf(elementData, size, Object[].class);
       } else {
       
           // replace with empty array.
           this.elementData = EMPTY_ELEMENTDATA; 
       }
   }

集合中的元素应放置在 Array 列表中。此代码将检查传递的集合的大小,如果大小大于零,则使用Arrays.copyOf()方法将集合复制到数组。如果传入构造函数的集合为 null,则抛出NullPointerException

例子:

Java
import java.util.ArrayList;
import java.util.Collection;
  
public class Main {
    public static void main(String args[])
    {
        Collection arr = new ArrayList();
        arr.add(1);
        arr.add(2);
        arr.add(3);
        arr.add(4);
        arr.add(5);
        System.out.println("This is arr " + arr);
        ArrayList newList
            = new ArrayList(arr);
        System.out.println("This is newList " + newList);
        newList.add(7);
        newList.add(700);
        System.out.println(
            "This is newList after adding elements "
            + newList);
    }
}


输出
This is arr [1, 2, 3, 4, 5]
This is newList [1, 2, 3, 4, 5]
This is newList after adding elements [1, 2, 3, 4, 5, 7, 700]

这里 arr 中的元素被传递给newList 。所以 arr 的元素被复制到 newList 这如上例所示。

ArrayList 的大小如何动态增长? [add() 方法]

让我们借助 ArrayList 的内部Java 8 代码深入了解如何在 Array 列表中添加方法 如果我们尝试在数组列表中使用 add() 方法在内部添加一个元素,那么它会检查是否有存储新元素的容量,如果不是,则按照 add() 的内部代码所示计算新容量) 方法。

public boolean add(E e) {
       ensureCapacityInternal(size + 1);  // Size Increments
       elementData[size++] = e;
       return true;
}                                              

 这里在 add(Object ) 方法中传递了对象并增加了大小。数组的内部容量由 ensureCapacityInternal() 方法保证

private void ensureCapacityInternal(int minCapacity) {

       if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
           minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
       }
       ensureExplicitCapacity(minCapacity);
}

这个 ensureExplicitCapacity 方法确定元素的当前大小和数组的最大大小。这里最小容量将是默认容量和最小容量的最大值,然后使用 ensureExplicitCapacity 方法 mincapacity 作为参数。

private void ensureExplicitCapacity(int minCapacity) {
       modCount++;
       
       // overflow-conscious code
       if (minCapacity - elementData.length > 0)
           grow(minCapacity);
}

这里如果 (mincapacity – arraylength) 大于 0(>0) 则数组大小将通过调用增长()方法和 mincapacity 作为参数来增长。

/**
    * Increases the capacity to ensure that it can hold at least the
    * number of elements specified by the minimum capacity argument.
    *
    * @param minCapacity the desired minimum capacity
    */
   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);
}

ArrayList 类中的增长方法给出了新的大小数组。在Java 8 及更高版本中,计算出比旧容量多 50% 的新容量,并且阵列增加了该容量。它使用 Arrays.copyOf ,它通过右移运算符将数组增加到新的长度,并且它会增加旧容量的 50%。

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

例如,如果数组大小为 10 并且所有房间都被元素填满,而我们现在添加新元素时,数组容量将增加为 10+ (10>>1) => 10+ 5 => 15. 这里的大小从 10 增加到 15。因此我们使用右移运算符将大小增加50% 。虽然在Java 6中它与上述增加 Array 大小的计算完全不同,但在Java 6 中容量增加了1.5 倍

int newCapacity = (oldCapacity * 3)/2 + 1;

remove 方法在 ArrayList中是如何工作的? [ArrayList 自动收缩]

要从Java中的 ArrayList 中删除元素,我们可以使用 remove(int i) [0 index based] 或 remove(Object o) 。从 ArrayList 中删除任何元素时,内部所有后续元素都将左移以填充由数组中删除的元素创建的间隙,然后从它们的索引中减去 1。数组的大小将减少 1 ( – – size )。

// Removes the element at the specified position in this list.
// Shifts any subsequent elements to the left (subtracts one from their indices).
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;
}

System.arrayCopy方法用于此目的。这里 index+1 是初始位置,index 是最终位置。由于位置索引处的元素被删除,因此从索引+1 开始的元素被复制到从索引开始的目的地。

System.arraycopy(elementData, index+1, elementData, index, numMoved);

这就是 ArrayList 自动收缩的方式。

创建 ArrayList 的最佳实践

每当我们创建一个 ArrayList 并达到其阈值时,Internally 会创建一个具有新容量的新 ArrayList 对象,并将旧 ArrayList 中的所有旧元素复制到一个新对象。这个过程会占用更多的空间和时间,即使它提供了灵活性。

临界点

Threshold = (Current Capacity) * (Load Factor)

负载因子是决定何时增加 ArrayList 容量的度量。 ArrayList 的默认加载因子是 0.75f。例如,当前容量为 10。因此,loadfactor = 10*0.75=7 而添加第 7元素数组大小会增加。因此,如果我们选择初始容量,将预期元素的数量保持为大约,这将是一个很好的做法。

ArrayList 的性能

ArrayList Java中常见操作的时间复杂度。

  • add():用于添加单个元素 O(1) 。在数组列表中添加 n 个元素需要 O(n)。
  • add( index, element ):在 O(n) 时间平均运行中添加特定索引中的元素
  • get():始终是一个常数时间 O(1) 的操作
  • remove():以线性 O(n) 时间运行。我们必须迭代整个数组以找到适合删除的元素
  • indexOf():它遍历整个数组,遍历每个元素,最坏的情况是数组的大小 n .so,它需要 O(n) 时间
  • contains():实现基于 indexOf()。所以它也将在 O(n) 时间内运行
  • sizeisEmptysetiteratorlistIterator操作以恒定时间 O(1) 运行

注意事项:

  • ArrayList 是Java中可调整大小的数组实现。
  • ArrayList 的后备数据结构是 Object 类的数组
  • 创建 ArrayList 时,您可以提供初始容量,然后使用给定容量声明数组。
  • 默认容量值为 10。如果用户未指定初始容量,则使用默认容量创建对象数组。
  • 当一个元素被添加到 ArrayList 时,它首先检查新元素是否有空间填充或需要增加内部数组的大小,如果必须增加容量,则计算新容量,该容量比旧容量,阵列增加了该容量。

参考: https://hg.openjdk。 Java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/ Java/util/ArrayList.Java