[Java from scratch to the end] No4.JDK1.8 ArrayList source code learning and expansion

ArrayList of JAVA

The book has its own golden house, and the book has its own beauty like jade
————————————————————————————————————
This article is in Based on the content of the book "Efficient Code: Java Development Manual", we will work with you to analyze and expand the ArrayList source code in the JDK1.8 version, and strive to do a comprehensive review of the knowledge points surrounding ArrayList.

In the previous picture, we are looking at the ArrayList part of the red box List today:
Insert image description here
red represents the interface, blue represents the abstract class, green represents the class in the concurrent package, and gray (Vector, Stack) represents the early thread-safe class (basically deprecated).

From the perspective of the inheritance and implementation interface relationship of ArrayList, the following figure will be more clear.
Insert image description here
List collection is the main implementation of linear data structure. Collection elements usually have clear previous and next
elements, as well as clear first element and The last element (so the implementation classes in our List family are all ordered). And the traversal results of the List collection are stable. The most commonly used collection classes in this system are ArrayList and LinkedList .
ArrayList Is a non-thread-safe collection whose capacity can be changed. The internal implementation uses arrays for storage. When the collection is expanded, a larger array space will be created and the original data will be copied to the new array. ArrayList supports fast random access to elements, but insertion and deletion are usually slow because the process is likely to require moving other elements.
————————————————————————————
The above is a summary of the characteristics of ArrayList. Let’s start learning the source code.

  1. Initialization of ArrayList
    Collection initialization usually involves allocating capacity, setting specific parameters and other related work. Briefly describe the work involved in initialization, and explain why in any case, you need to explicitly set the initial size of the collection container .
    Go directly to the code:
/**
 * Default initial capacity.
 * 默认初始容量为10
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 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 = {
    
    };

/**
 * 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.
 * 这就是我们存储 ArrayList 真正数据的数组
 * transient 关键字我们后边再聊
 */
transient Object[] elementData; // non-private to simplify nested class access

/**
 * The size of the ArrayList (the number of elements it contains).
 *
 * @serial
 * 数组的大小,也可以理解为数组中存储数据单元的个数。
 */
private int size;

There is nothing special to explain about the internal property values ​​of ArrayList. Let’s take a look at the construction method:

/**
* Constructs an empty list with an initial capacity of ten.
* 构造初始容量为10的空列表
* 在1.8之前,默认的无参构造容量为10,在1.8后默认的构造容量为0,在第一次add一个元素时会对容量进行一个分配,容量为默认值10,后边会详细说明。
*/
public ArrayList() {
    
    
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
* Constructs an empty list with the specified initial capacity.
*
* @param  initialCapacity  the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
*         is negative
* 初始带容量的构造方法,简单易懂
* 当你传的值大于0,则就按照你穿的值设定数组的初始容量
* 当你传的值等于0,则使用默认的空数组 EMPTY_ELEMENTDATA
* 当你传的值小于0,抛出非法参数异常
*/
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);
   }
}

/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
    
    
   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;
   }
}
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

The last construction method actually doesn’t have much code, but I think it needs to be emphasized a little. I only understood it after reading books and checking information. The main thing I want to explain is the reason for this comment // c.toArray might (incorrectly) not return Object
[ ] (see 6260652)
Many friends may be a little confused when they see this code like I was when I saw it for the first time. What a thing this is. After checking it, I found that it is an official JAVA bug. Attached is a website with information about bug checking.
Java Bug Database
see 6260652 is his bug ID. It is said that it has been solved in 1.9. The solution for 1.8 here is to add a judgment at the end. For example, in the following code, you need to judge whether the array type is Object. If not, you need to Convert it to Object type, and then copy and assign it.

       if (elementData.getClass() != Object[].class)
           elementData = Arrays.copyOf(elementData, size, Object[].class);

Then the problem with this bug is as mentioned in the comments, because the c.toArray() method does not necessarily return an Object array , so there is a problem. Let’s take a closer look below.
First, determine the array object that stores data in our ArrayList collection. elementData is an Object array, and the Object array object reference in Java can point to a non-Object array. This is the problem. Let’s look at the code.

// 首先我们创建一个类myList继承自ArrayList,并重写toArray()方法
// 使得场景先满足第一个条件,toArray()方法返回的不是Object数组
public class MyList<E> extends ArrayList<E> {
    
    
	private static final long serialVersionUID = 8068784835731821475L;
	@Override
	public String[] toArray() {
    
    
		// 这里把返回值先写死,方便
		return new String[] {
    
    "1"};
	}
}
// 接下来看这一段
public static void main(String[] args) {
    
    
	// 创建一个MyList
	MyList<String> mylist = new MyList<>();
	
	// 模拟我们的elementData Object数据
	// 并将返回的String[] 给到 elementData对象。
	// 这里不会有问题,并且elementData看起来还是像一个存储Object对象类型的数组
	Object[] elementData = mylist.toArray();
	// 接下来我们给这个看起来像Object数组的elementData赋一个Integer的值
	elementData[0] = new Integer(1);
	// 最后会报一个错
	// Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
	// 原因就是这里的elementData对象虽然看起来像是一个Object数组,但是他底层
	// 指向的对象已经是一个String类型的数组了,无法再加入String类型以外的对象
	// 所以集合中我们需要保持elementData数组对象指向的是一个Object数组
	// 而这个数组能加入什么类型的元素让泛型去决定,保持数组元素类型的一致性。
}

After reading the construction method, let's take a look at why everyone is saying that the addition and deletion speed of the ArrayList collection is slow. Let's go step by step here. First, we will explain the Arrays class and quote the content in the book.

Arrays is a tool class for operating on array objects, including array sorting, search, comparison, copy
and other operations. Sorting, in particular, is constantly evolving in multiple JDK versions. For example, the original merge sort was changed to
Timsort, which significantly improved the sorting performance of the collection. In addition, you can also convert arrays into sets through this tool class.

Because the bottom layer of ArrayList is an array, we can often find the Arrays tool class in the source code of ArrayList. Let's first take a look at the simplest parameterless method add() to add a separate object. Here I also list the related methods it uses and explain them directly in the remarks.

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 * add中主要的方法就是ensureCapacityInternal()
 * 除了这个方法就直接赋值了,所以我们主要看这个方法干了啥
 * 这方法里边其实还调了几个方法,我们把他们直接进行编号①→②→③→④
 */
public boolean add(E e) {
    
    
	// 给①传递的参数是当前存储的元素个数加1
	// 这里强调一下,这个size就是elementData数组当前存储元素的个数
	// 和elementData.length是不一样的,length是表示数组长度,就是最多能存多少元素
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

// ①
// 【ensure→确保 Capacity→容量 Internal→内部】 :确保内部容量,方法没有返回值
// 也就是说假如你之前数组已经存满了,如果没调用这个方法,
// 再往里边加数据 elementData[size++] = e;的话,就加不进去了吧,而且会下表越界
// 所以这里这个方法,里边肯定会有对容量的判断及修改。我们先看③
private void ensureCapacityInternal(int minCapacity) {
    
    
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// ②
// 【ensure→确保 Explicit→明确的 Capacity→容量】 :确保明确的,正确的容量,方法没有返回值
// 参数就是②返回的容量数
private void ensureExplicitCapacity(int minCapacity) {
    
    
	// 这个是父类的属性,用来记录修改次数,和这里的逻辑没有关系,暂时忽略
    modCount++;

    // overflow-conscious code
    // ②方法为我们返回了最新的容量,也就是现在数组中需要存储的元素个数
    // 但是我们得确保元素在数组中能否放的下,先简单重复一下,
    // 我们java中的数组类型,初始化后容量无法改变,我们知道ArrayList的底层是使用数组的,而ArrayList集合的数量大小又是可以改变的,是怎么做到的呢
    // 答案就是使用Arrays工具类,创建一个扩容后大的数组,把原数组内容拷贝过去,就是生成了一个新的数组,就是扩容拷贝这一步导致了在某些新增时间段ArrayList的速度慢。
    if (minCapacity - elementData.length > 0)
    	// 这里判断最新容量是否大过了数组长度,如果超过了,就进行扩容拷贝
    	// 如果没有超过,就直接在数组的对应位置上赋值就可以了,这个新增速度还是可以的
    	// 具体的扩容方法在grow()方法中,我们接下来就去看④
        grow(minCapacity);
}

// ③
//【calculate→计算 Capacity→容量】 :计算容量,静态方法,有返回值
// 入力参数为我们【存数据的数组elementData】和【最新的追加一个元素之后的元素个数】。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
    
	// 这里先判断elementData数组是不是空数组,当我们用默认空参数的构造方法构建时,elementData就是空数组。
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
    	// 如果是空数组,使用传进来的最新元素个数和默认值为10的定值进行比较,返回较大值
    	// 这里就是我们经常说的默认数组容量的分配地点了
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 如果不是空数组就把最新的容量值返回
    // 我们再看②
    return minCapacity;
	// 方法的最终目的就是返回新增元素后的容量大小,默认最小容量为10。
}

/** 
 * ④
 * 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;
    // 扩容,获得最新的容量值newCapacity,其值为旧的数组长度加上一个值,我附上一段书上的备注:JDK6之前扩容50%或50%-1,但是取ceil,而之后版本取floor
    // 这里(oldCapacity >> 1)的值可以理解为oldCapacity的一半
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 正数带符号右移的值肯定是正值,所以oldCapacity+(oldCapacity >> 1)的结果可能超过int可以表示的最大值,反而有可能比参数minCapacity更小
    // 所以我们需要判断newCapacity和参数minCapacity的大小
    if (newCapacity - minCapacity < 0)
    	// 如果newCapacity大小确实超过了int可以表示的最大值
    	// 反而此时比minCapacity更小,则此时容量值就直接设置为minCapacity的值
        newCapacity = minCapacity;
    // 再下来判断此时的新容量newCapacity是否超过了数组最大长度
    // → MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    	// 如果超过了就调用hugeCapacity方法来得到更大的容量
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    // 翻译:最小容量通常接近大小,所以这是一个胜利
    // 这里就是我们上边所说的进行扩容拷贝了,返回值是Object[]
    elementData = Arrays.copyOf(elementData, newCapacity);
}

To summarize:
In fact, the speed of ArrayList's parameterless insertion method is okay most of the time, because the insertions here are inserted at the end of the array. Except for the time when expansion is required, most of the time there is no need to copy data to another array. Copying data across arrays is the fundamental reason for slowness. Just imagine, when ArrayList is constructed with no parameters, the default size is 10, which means that the capacity of 10 is allocated when adding for the first time, and Arrays will be called for each subsequent expansion. .copyOf method, creates a new array and then copies it. As you can imagine, if you need to place 1,000 elements in an ArrayList, using the default construction method and adding them one by one, you will need to passively expand the capacity 13 times to complete the storage. On the contrary, if the capacity new ArrayList(1000) is specified during initialization, then 1000 storage space will be allocated directly when initializing the ArrayList object, thus avoiding the additional overhead of passive expansion and array copying. Finally, further imagine that if this value reaches a larger magnitude and the initial capacity allocation problem is not paid attention to, the performance loss caused will be very large and even lead to the risk of OOM. (out of memory memory overflow)
In addition, I would like to emphasize that the add(int index, E element) method with parameters is slower than the above method without parameters, because when you add elements in the middle, you must first add the array Divided into two, the elements in the array behind the currently added element need to be copied to the next position, which is slow. code show as below

    public void add(int index, E element) {
    
    
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 主要是这一步数组拷贝导致速度下降
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

As for the addAll() method, we will not continue to look at it. It is similar to the add() method, except that the amount of data added is inconsistent. The implementation is the same routine. Interested children can go and take a look for themselves.
Well, we have already looked at the insertion method. Let’s look at the deletion method↓
Deletion method. We still only look at deleting a single element. The principles of deleting multiple elements are similar.
Here are three methods listed here, ①remove(int index), ②remove(Object o), and the internal private method ③fastRemove(int index) used by these two methods, see the code directly below (the original comments that were too long were deleted by me)

/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).
 *
 * @param index the index of the element to be removed
 * @return the element that was removed from the list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * ①
 * 参数是要删除的元素的下标
 */
public E remove(int index) {
    
    
	// 这里rangeCheck方法是判断下标是否越界的
    rangeCheck(index);

    modCount++;
    // remove方法最后会返回被删除掉的元素,就是在这里获取的
    E oldValue = elementData(index);

	// 这个numMoved变量就是在我们删除元素之后需要移动的元素数量
	// 写为size - (index + 1)可能比较好理解一点,假如你集合中现在有十个元素
	// 你现在要删除第九个元素,下标为8,通过计算得出numMoved为1,也就是需要
	// 向前移动一个元素,就是原先集合的第十个元素。
    int numMoved = size - index - 1;
    // 这里什么情况下不需要移动元素呢,就是当你删除的是最后一个元素时。
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 这里删除方法就比较残暴,直接指向null,让垃圾回收器去回收已经删除的元素。
    elementData[--size] = null; // clear to let GC do its work

	// 这里我们可以发现的就是,ArrayList无论是插入还是删除,只要涉及到元素的拷贝
	// 就会慢,当你删除或者插入元素在ArrayList尾部的时候,其实速度还是可以的。
	
    return oldValue;
}

/**
 * @param o element to be removed from this list, if present
 * @return <tt>true</tt> if this list contained the specified element
 * ②
 * 参数就是要删除的元素的对象
 */
public boolean remove(Object o) {
    
    
	// 首先我们ArrayList是能存null的,当然也能删除null
	// 把这个判断单独提出来就是为了防止空指针,因为判断元素相等是需要比较的
    if (o == null) {
    
    
    	// 至于为什么要循环,因为我们删除数组元素还是需要他的下标的,这里速度就已经有慢的隐患了
        for (int index = 0; index < size; index++)
        	// 这里其实只能删除掉第一个值为null的元素
        	// fastRemove在下边再看
            if (elementData[index] == null) {
    
    
                fastRemove(index);
                return true;
            }
    } else {
    
    
        for (int index = 0; index < size; index++)
        	// 这里就是删除一个不是null值元素的地方了
            if (o.equals(elementData[index])) {
    
    
            	// 主要就一个fastRemove方法,我们看下边
                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) {
    
    
	// 惊不惊喜,意不意外,这里fastRemove和remove(int index)方法几乎一模一样,唯一的区别就是fastRemove方法里不需要下标越界的判断了。。。
    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
}

Here we finally found that the two remove methods above can only delete one element at a time. Even if the second remove method parameter passes an Object, what if we need to delete multiple consistent values ​​​​in the collection? What to do, the answer is to use the removeAll(Collection<?> c) ​​method, which will delete all elements that are consistent with the value in the array you passed in. Take a look at the test code:

List<String> list1 = new ArrayList<String>();
list1.add("aaa");
list1.add(null);
list1.add("bbb");
list1.add(null);

System.out.println(list1.size());
System.out.println(list1);

List<String> list2 = new ArrayList<String>();
list2.add(null);

list1.removeAll(list2);
System.out.println(list1.size());
System.out.println(list1);

输出结果如下
4
[aaa, null, bbb, null]
2
[aaa, bbb]

I have basically finished reading the source code here. Let’s go down to our extension content, which is still related to ArrayList, mainly the relationship between arrays and collections.

Arrays and collections are both containers used to store objects. The former is simple in nature and convenient to use, while the latter is type-safe and powerful, and there must be a way to convert each other between the two. After all, they have very different personalities. During the conversion process, if you do not pay attention to the implementation method behind the conversion, it is easy to cause unexpected problems.

Next, let’s talk about the mutual conversion between arrays and sets.
The first is the first case, converting an array into a collection. Take Arrays.asList() as an example. When it converts an array into a collection, its methods related to modifying the collection cannot be used. The add/remove/clear method throws UnsupportedOperationException. The sample code is as follows:

String[] stringArray = new String[3];
stringArray[0] = "aaa";
stringArray[1] = "bbb";
stringArray[2] = "ccc";

List<String> list1 = Arrays.asList(stringArray);
list1.set(0, "uzi out");
System.out.println(list1.get(0));

// 以下三行编译正确,执行都会报错
list1.add("ddd");
list1.remove(1);
list1.clear();

执行结果为:
uzi out
Exception in thread "main" java.lang.UnsupportedOperationException

There is no problem with the set method here, but why can't you use the add/remove/clear method? Because Arrays.asList embodies the adapter mode, the background data is still the original array, and the set() method indirectly values ​​the array. modification operation. The return object of asList is an internal class of Arrays, which does not implement the related modification method of the collection number, which is why the exception is thrown.
The source code of Arrays.asList is as follows:

public static <T> List<T> asList(T... a) {
    
    
    return new ArrayList<>(a);
}

What is returned is obviously an ArrayList object, so why can't this collection be modified as desired?
Note that this ArrayList is not that ArrayList. Although Arrays and ArrayList belong to the same package,
an inner class of ArrayList is also defined in the Arrays class (perhaps named InnerArrayList for easier identification). According to the scope proximity principle, the ArrayList here is Li Gui, that is, this is an inner class. Compared with 1.6 and 1.7, Li Gui has added many implementation methods, but there is still no implementation of the add/remove/clear method. Most of them are implemented by some methods of traversing the value and changing the value. The code is as follows:

/**
* @serial include
*/
private static class ArrayList<E> extends AbstractList<E>
   implements RandomAccess, java.io.Serializable
{
    
    
   private static final long serialVersionUID = -2764017481108945198L;
   private final E[] a;

   ArrayList(E[] array) {
    
    
       a = Objects.requireNonNull(array);
   }

   @Override
   public int size() {
    
    
       return a.length;
   }

   @Override
   public Object[] toArray() {
    
    
       return a.clone();
   }

   @Override
   @SuppressWarnings("unchecked")
   public <T> T[] toArray(T[] a) {
    
    
       int size = size();
       if (a.length < size)
           return Arrays.copyOf(this.a, size,
                                (Class<? extends T[]>) a.getClass());
       System.arraycopy(this.a, 0, a, 0, size);
       if (a.length > size)
           a[size] = null;
       return a;
   }

   @Override
   public E get(int index) {
    
    
       return a[index];
   }

   @Override
   public E set(int index, E element) {
    
    
       E oldValue = a[index];
       a[index] = element;
       return oldValue;
   }

   @Override
   public int indexOf(Object o) {
    
    
       E[] a = this.a;
       if (o == null) {
    
    
           for (int i = 0; i < a.length; i++)
               if (a[i] == null)
                   return i;
       } else {
    
    
           for (int i = 0; i < a.length; i++)
               if (o.equals(a[i]))
                   return i;
       }
       return -1;
   }

   @Override
   public boolean contains(Object o) {
    
    
       return indexOf(o) != -1;
   }

   @Override
   public Spliterator<E> spliterator() {
    
    
       return Spliterators.spliterator(a, Spliterator.ORDERED);
   }

   @Override
   public void forEach(Consumer<? super E> action) {
    
    
       Objects.requireNonNull(action);
       for (E e : a) {
    
    
           action.accept(e);
       }
   }

   @Override
   public void replaceAll(UnaryOperator<E> operator) {
    
    
       Objects.requireNonNull(operator);
       E[] a = this.a;
       for (int i = 0; i < a.length; i++) {
    
    
           a[i] = operator.apply(a[i]);
       }
   }

   @Override
   public void sort(Comparator<? super E> c) {
    
    
       Arrays.sort(a, c);
   }
}

The final keyword on the array forces the array reference to always point to the original array, and you will find that it does not implement the add/remove/clear method. As for this UnsupportedOperationException, it is reported by its parent class AbstractList. So, if you just
Insert image description here
view If you want to modify the converted collection, please use the following code:

List<Object> objectList = new java.util.ArrayList<Object>(Arrays.asList(数组));

Let's take a look at converting a set to an array.
In fact, converting a set to an array is simpler and more controllable, but we still need to pay attention to some issues. Let's look at the code.

List<String> list = new ArrayList<String>();
list.add("aaa"); 
list.add("bbb"); 
list.add("ccc"); 

// 第一处
// 泛型丢失,无法使用String[]来接受返回的结果,因为toArray()方法返回的是object[]
Object[] arrayl = list.toArray(); 

// 第二处
// 数组长度小于集合元素个数
String[] array2 = new String[2] ; 
list.toArray(array2); 
System.out.println(Arrays.asList(array2)) ; 

// 第三处
// 数组长度等于集合元素个数
String[] array3 = new String[3]; 
list.toArray(array3);
System.out.println(Arrays.asList(array3));

The execution results are as follows
[null, null]
[aaa, bbb, ccc]

The first point is easy to understand. Do not use the toArray() parameterless method to convert a collection into an array. This will cause the loss of generics. In the second place, the compilation is correct and the operation is correct, but the result is null. The third point is that the collection elements are successfully copied into the array. The difference between 2 and 3 lies in whether the capacity of the array to be copied is sufficient. If the capacity is not enough, discard this array and start a new one. The source code for this method is as follows:

@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    
    
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

So the second point will successfully copy the collection elements into an array and return the array, but this time it has nothing to do with the insufficient capacity of the array you passed in.

At this point, let’s talk about the transient keyword again, transient Object[ ] elementData. The collection where we actually store data is modified by transient. This keyword indicates that the object modified by this field will be serialized when the class is serialized. Ignore . Because the system will call writeObject to write to the stream when the collection is serialized, and when the network client deserializes readObject, it will be reassigned to the elementData of the new object. Why is this unnecessary? Because the capacity of elementData is often greater than the number of actual stored elements, so you only need to send array elements that actually have actual values.

Also, let’s test three situations: when the input parameter group capacity is not enough, when the input parameter group capacity is just right, and when the input parameter group capacity exceeds the set size, and record the execution time. I will take a screenshot here, so I won’t Knocked on
Insert image description here
Insert image description here
So, when we convert a collection into an array, if we are not going to add elements to the array, please try to give an array the same size as the collection, which will be most efficient.
Finally, let’s talk about subList

public List<E> subList(int fromIndex, int toIndex) {
    
    
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

This method also returns an internal class, and note that it does not implement the serialization interface, so be careful of pitfalls.
Insert image description here

Finally, to summarize,
ArrayList is the most commonly used List implementation class. It is implemented internally through an array, which allows fast random access to elements. The disadvantage of the array is that there cannot be a gap between each element. When the array size is not satisfied and the storage capacity needs to be increased, the data of the existing array must be copied to the new storage space. When inserting or deleting elements from the middle of the ArrayList, the array needs to be copied and moved, which is relatively expensive. Therefore, it is suitable for random search and traversal, but not for insertion and deletion.


The content of ArrayList probably ends here. I am very grateful to the book "Efficient Code: Java Development Manual" and the Alibaba boss who wrote this book. I will continue to learn and see you next time.

Guess you like

Origin blog.csdn.net/cjl836735455/article/details/106480541