【Java基础知识总结 | 第三篇】深入理解分析ArrayList源码

在这里插入图片描述

文章目录

  • 3.深入理解分析ArrayList源码
    • 3.1ArrayList简介
    • 3.2ArrayLisy和Vector的区别?
    • 3.3ArrayList核心源码解读
      • 3.3.1ArrayList存储机制
        • (1)构造函数
        • (2)add()方法
        • (3)新增元素大体流程
      • 3.3.2ArrayList扩容机制
        • (1)grow()方法
        • (2)hugeCapacity()方法
      • 3.3.3 辨别System.arraycopy()和Arrays.copyOf()方法
        • (1)System.arraycopy()方法
        • (2)Arrays.copyOf()方法
        • (3)两者联系和区别
      • 3.3.4分析ensureCapacity()方法
    • 3.4ArrayList总结
      • 3.4.1ArrayList特点
      • 3.4.2ArrayList的存储、扩容机制

3.深入理解分析ArrayList源码

3.1ArrayList简介

  1. ArrayList 的底层是数组队列,相当于动态数组。

  2. 与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

  3. ArrayList 继承于 AbstractList ,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。

    public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    }
    
    • List : 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。
    • RandomAccess :这是一个标志接口,表明实现这个接口的 List 集合是支持 快速随机访问 的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问
    • Cloneable :表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作
    • Serializable : 表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输,非常方便

ArrayList 类图

3.2ArrayLisy和Vector的区别?

  • ArrayListList 的主要实现类,底层使用 Object[]存储,适用于频繁的查找工作,线程不安全 。
  • VectorList 的古老实现类,底层使用Object[] 存储,线程安全。

3.3ArrayList核心源码解读

以 JDK1.8 为例,分析一下 ArrayList 的底层源码:

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable {private static final long serialVersionUID = 8683452581122892189L;/*** 默认初始容量大小*/private static final int DEFAULT_CAPACITY = 10;/*** 空数组(用于空实例)。*/private static final Object[] EMPTY_ELEMENTDATA = {};//用于默认大小空实例的共享空数组实例。//我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/*** 保存ArrayList数据的数组*/transient Object[] elementData; // non-private to simplify nested class access/*** ArrayList 所包含的元素个数*/private int size;/*** 带初始容量参数的构造函数(用户可以在创建ArrayList对象时自己指定集合的初始大小)*/public ArrayList(int initialCapacity) {if (initialCapacity > 0) {//如果传入的参数大于0,创建initialCapacity大小的数组this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {//如果传入的参数等于0,创建空数组this.elementData = EMPTY_ELEMENTDATA;} else {//其他情况,抛出异常throw new IllegalArgumentException("Illegal Capacity: " +initialCapacity);}}/*** 默认无参构造函数* DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10*/public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}/*** 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。*/public ArrayList(Collection<? extends E> c) {//将指定集合转换为数组elementData = c.toArray();//如果elementData数组的长度不为0if ((size = elementData.length) != 0) {// 如果elementData不是Object类型数据(c.toArray可能返回的不是Object类型的数组所以加上下面的语句用于判断)if (elementData.getClass() != Object[].class)//将原来不是Object类型的elementData数组的内容,赋值给新的Object类型的elementData数组elementData = Arrays.copyOf(elementData, size, Object[].class);} else {// 其他情况,用空数组代替this.elementData = EMPTY_ELEMENTDATA;}}/*** 修改这个ArrayList实例的容量是列表的当前大小。 应用程序可以使用此操作来最小化ArrayList实例的存储。*/public void trimToSize() {modCount++;if (size < elementData.length) {elementData = (size == 0)? EMPTY_ELEMENTDATA: Arrays.copyOf(elementData, size);}}
//下面是ArrayList的扩容机制
//ArrayList的扩容机制提高了性能,如果每次只扩充一个,
//那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。/*** 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量** @param minCapacity 所需的最小容量*/public void ensureCapacity(int minCapacity) {//如果是true,minExpand的值为0,如果是false,minExpand的值为10int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)// any size if not default element table? 0// larger than default for default empty table. It's already// supposed to be at default size.: DEFAULT_CAPACITY;//如果最小容量大于已有的最大容量if (minCapacity > minExpand) {ensureExplicitCapacity(minCapacity);}}// 根据给定的最小容量和当前数组元素来计算所需容量。private static int calculateCapacity(Object[] elementData, int minCapacity) {// 如果当前数组元素为空数组(初始情况),返回默认容量和最小容量中的较大值作为所需容量if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}// 否则直接返回最小容量return minCapacity;}// 确保内部容量达到指定的最小容量。private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}//判断是否需要扩容private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)//调用grow方法进行扩容,调用此方法代表已经开始扩容了grow(minCapacity);}/*** 要分配的最大数组大小*/private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/*** ArrayList扩容的核心方法。*/private void grow(int minCapacity) {// oldCapacity为旧容量,newCapacity为新容量int oldCapacity = elementData.length;//将oldCapacity 右移一位,其效果相当于oldCapacity /2,//我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,int newCapacity = oldCapacity + (oldCapacity >> 1);//然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,if (newCapacity - minCapacity < 0)newCapacity = minCapacity;//再检查新容量是否超出了ArrayList所定义的最大容量,//若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,//如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。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);}//比较minCapacity和 MAX_ARRAY_SIZEprivate static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}/*** 返回此列表中的元素数。*/public int size() {return size;}/*** 如果此列表不包含元素,则返回 true 。*/public boolean isEmpty() {//注意=和==的区别return size == 0;}/*** 如果此列表包含指定的元素,则返回true 。*/public boolean contains(Object o) {//indexOf()方法:返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1return indexOf(o) >= 0;}/*** 返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1*/public int indexOf(Object o) {if (o == null) {for (int i = 0; i < size; i++)if (elementData[i] == null)return i;} else {for (int i = 0; i < size; i++)//equals()方法比较if (o.equals(elementData[i]))return i;}return -1;}/*** 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。.*/public int lastIndexOf(Object o) {if (o == null) {for (int i = size - 1; i >= 0; i--)if (elementData[i] == null)return i;} else {for (int i = size - 1; i >= 0; i--)if (o.equals(elementData[i]))return i;}return -1;}/*** 返回此ArrayList实例的浅拷贝。 (元素本身不被复制。)*/public Object clone() {try {ArrayList<?> v = (ArrayList<?>) super.clone();//Arrays.copyOf功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度v.elementData = Arrays.copyOf(elementData, size);v.modCount = 0;return v;} catch (CloneNotSupportedException e) {// 这不应该发生,因为我们是可以克隆的throw new InternalError(e);}}/*** 以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。* 返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。* 因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。*/public Object[] toArray() {return Arrays.copyOf(elementData, size);}/*** 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素);* 返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。* 否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。* 如果列表适用于指定的数组,其余空间(即数组的列表数量多于此元素),则紧跟在集合结束后的数组中的元素设置为null 。* (这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。)*/@SuppressWarnings("unchecked")public <T> T[] toArray(T[] a) {if (a.length < size)// 新建一个运行时类型的数组,但是ArrayList数组的内容return (T[]) Arrays.copyOf(elementData, size, a.getClass());//调用System提供的arraycopy()方法实现数组之间的复制System.arraycopy(elementData, 0, a, 0, size);if (a.length > size)a[size] = null;return a;}// Positional Access Operations@SuppressWarnings("unchecked")E elementData(int index) {return (E) elementData[index];}/*** 返回此列表中指定位置的元素。*/public E get(int index) {rangeCheck(index);return elementData(index);}/*** 用指定的元素替换此列表中指定位置的元素。*/public E set(int index, E element) {//对index进行界限检查rangeCheck(index);E oldValue = elementData(index);elementData[index] = element;//返回原来在这个位置的元素return oldValue;}/*** 将指定的元素追加到此列表的末尾。*/public boolean add(E e) {ensureCapacityInternal(size + 1);  // Increments modCount!!//这里看到ArrayList添加元素的实质就相当于为数组赋值elementData[size++] = e;return true;}/*** 在此列表中的指定位置插入指定的元素。* 先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;* 再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。*/public void add(int index, E element) {rangeCheckForAdd(index);ensureCapacityInternal(size + 1);  // Increments modCount!!//arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己System.arraycopy(elementData, index, elementData, index + 1,size - index);elementData[index] = element;size++;}/*** 删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。*/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;}/*** 从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。* 返回true,如果此列表包含指定的元素*/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;}/** Private remove method that skips bounds checking and does not* return the value removed.*/private void fastRemove(int index) {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}/*** 从列表中删除所有元素。*/public void clear() {modCount++;// 把数组中所有的元素的值设为nullfor (int i = 0; i < size; i++)elementData[i] = null;size = 0;}/*** 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。*/public boolean addAll(Collection<? extends E> c) {Object[] a = c.toArray();int numNew = a.length;ensureCapacityInternal(size + numNew);  // Increments modCountSystem.arraycopy(a, 0, elementData, size, numNew);size += numNew;return numNew != 0;}/*** 将指定集合中的所有元素插入到此列表中,从指定的位置开始。*/public boolean addAll(int index, Collection<? extends E> c) {rangeCheckForAdd(index);Object[] a = c.toArray();int numNew = a.length;ensureCapacityInternal(size + numNew);  // Increments modCountint numMoved = size - index;if (numMoved > 0)System.arraycopy(elementData, index, elementData, index + numNew,numMoved);System.arraycopy(a, 0, elementData, index, numNew);size += numNew;return numNew != 0;}/*** 从此列表中删除所有索引为fromIndex (含)和toIndex之间的元素。* 将任何后续元素移动到左侧(减少其索引)。*/protected void removeRange(int fromIndex, int toIndex) {modCount++;int numMoved = size - toIndex;System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);// clear to let GC do its workint newSize = size - (toIndex - fromIndex);for (int i = newSize; i < size; i++) {elementData[i] = null;}size = newSize;}/*** 检查给定的索引是否在范围内。*/private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}/*** add和addAll使用的rangeCheck的一个版本*/private void rangeCheckForAdd(int index) {if (index > size || index < 0)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}/*** 返回IndexOutOfBoundsException细节信息*/private String outOfBoundsMsg(int index) {return "Index: " + index + ", Size: " + size;}/*** 从此列表中删除指定集合中包含的所有元素。*/public boolean removeAll(Collection<?> c) {Objects.requireNonNull(c);//如果此列表被修改则返回truereturn batchRemove(c, false);}/*** 仅保留此列表中包含在指定集合中的元素。* 换句话说,从此列表中删除其中不包含在指定集合中的所有元素。*/public boolean retainAll(Collection<?> c) {Objects.requireNonNull(c);return batchRemove(c, true);}/*** 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。* 指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。* 返回的列表迭代器是fail-fast 。*/public ListIterator<E> listIterator(int index) {if (index < 0 || index > size)throw new IndexOutOfBoundsException("Index: " + index);return new ListItr(index);}/*** 返回列表中的列表迭代器(按适当的顺序)。* 返回的列表迭代器是fail-fast 。*/public ListIterator<E> listIterator() {return new ListItr(0);}/*** 以正确的顺序返回该列表中的元素的迭代器。* 返回的迭代器是fail-fast 。*/public Iterator<E> iterator() {return new Itr();}

3.3.1ArrayList存储机制

(1)构造函数

ArrayList 有三种方式来初始化,构造方法源码如下(JDK8):

/*** 默认初始容量大小*/
private static final int DEFAULT_CAPACITY = 10;private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/*** 默认构造函数,使用初始容量10构造一个空列表(无参数构造)*/
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}/*** 带初始容量参数的构造函数。(用户自己指定容量)*/
public ArrayList(int initialCapacity) {if (initialCapacity > 0) {//初始容量大于0//创建initialCapacity大小的数组this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {//初始容量等于0//创建空数组this.elementData = EMPTY_ELEMENTDATA;} else {//初始容量小于0,抛出异常throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);}
}/***构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回*如果指定的集合为null,throws NullPointerException。*/
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;}
}

以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10 下面在我们分析 ArrayList 扩容时会讲到这一点内容!

补充:JDK6 new 无参构造的 ArrayList 对象时,直接创建了长度是 10 的 Object[] 数组 elementData

(2)add()方法
  • add()源码:
/**
* 将指定的元素追加到此列表的末尾。
*/
public boolean add(E e) {// 加元素之前,先调用ensureCapacityInternal方法ensureCapacityInternal(size + 1);  // Increments modCount!!// 这里看到ArrayList添加元素的实质就相当于为数组赋值elementData[size++] = e;return true;
}
  • ensureCapacityInternal()源码:
// 根据给定的最小容量和当前数组元素来计算所需容量。
private static int calculateCapacity(Object[] elementData, int minCapacity) {// 如果当前数组元素为空数组(初始情况),返回默认容量:10和最小容量中的较大值作为所需容量if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}// 否则直接返回最小容量return minCapacity;
}// 确保内部容量达到指定的最小容量。
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

ensureCapacityInternal 方法非常简单,内部直接调用了 ensureExplicitCapacity 方法:

//判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {	//mminCapacity为最小所需容量modCount++;//判断当前数组容量是否足以存储minCapacity个元素			//数组位置不足if (minCapacity - elementData.length > 0)//调用grow方法进行扩容grow(minCapacity);
}
(3)新增元素大体流程
  • 当我们要 add 进第 1 个元素到 ArrayList 时,elementData.length 为 0 (因为还是一个空的 list),因为执行了 ensureCapacityInternal() 方法 ,所以 minCapacity 此时为 10。此时,minCapacity - elementData.length > 0成立,所以会进入 grow(minCapacity) 方法。
  • add 第 2 个元素时,minCapacity 为 2,此时 elementData.length(容量)在添加第一个元素后扩容成 10 了。此时,minCapacity - elementData.length > 0 不成立,所以不会进入 (执行)grow(minCapacity) 方法。
  • 添加第 3、4···到第 10 个元素时,依然不会执行 grow 方法,数组容量都为 10。

直到添加第 11 个元素,minCapacity(为 11)比 elementData.length(为 10)要大。进入 grow 方法进行扩容

3.3.2ArrayList扩容机制

(1)grow()方法
/*** 要分配的最大数组大小*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/*** ArrayList扩容的核心方法。*/
private void grow(int minCapacity) {// oldCapacity为旧容量,newCapacity为新容量int oldCapacity = elementData.length;// 将oldCapacity 右移一位,其效果相当于oldCapacity /2,// 我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,int newCapacity = oldCapacity + (oldCapacity >> 1);// 然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,if (newCapacity - minCapacity < 0)newCapacity = minCapacity;// 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,// 如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。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);
}

int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity 为偶数就是 1.5 倍,否则是 1.5 倍左右)! 奇偶不同,比如:10+10/2 = 15, 33+33/2=49。如果是奇数的话会丢掉小数.

  • 例子:
    • add 第 1 个元素时,oldCapacity 为 0,经比较后第一个 if 判断成立(扩容后的数组容量还是小于所需容量)newCapacity = minCapacity(为 10)。但是第二个 if 判断不会成立,即 ==newCapacity 不比 MAX_ARRAY_SIZE 大,==则不会进入 hugeCapacity 方法。数组容量为 10,add 方法中 return true,size 增为 1。
    • add 第 11 个元素进入 grow 方法时,newCapacity 为 15,比 minCapacity(为 11)大,第一个 if 判断不成立。新容量没有大于数组最大 size,不会进入 hugeCapacity 方法。数组容量扩为 15,add 方法中 return true,size 增为 11。
    • 以此类推······
(2)hugeCapacity()方法

从上面 grow() 方法源码我们知道:如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity() 方法来比较 minCapacityMAX_ARRAY_SIZE,如果 minCapacity 大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8

private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();// 对minCapacity和MAX_ARRAY_SIZE进行比较// 若minCapacity大,将Integer.MAX_VALUE作为新数组的大小// 若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小// MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;
}

3.3.3 辨别System.arraycopy()和Arrays.copyOf()方法

阅读源码的话,我们就会发现 ArrayList 中大量调用了这两个方法。比如:我们上面讲的扩容操作以及add(int index, E element)toArray() 等方法中都用到了该方法!

(1)System.arraycopy()方法
  • 源码:
    // 我们发现 arraycopy 是一个 native 方法,接下来我们解释一下各个参数的具体意义/***   复制数组* @param src 源数组* @param srcPos 源数组中的起始位置* @param dest 目标数组* @param destPos 目标数组中的起始位置* @param length 要复制的数组元素的数量*/public static native void arraycopy(Object src,  int  srcPos,Object dest, int destPos,int length);
  • 场景:
    /*** 在此列表中的指定位置插入指定的元素。*先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;*再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。*/public void add(int index, E element) {rangeCheckForAdd(index);ensureCapacityInternal(size + 1);  // Increments modCount!!//arraycopy()方法实现数组自己复制自己//elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量;System.arraycopy(elementData, index, elementData, index + 1, size - index);elementData[index] = element;size++;}
  • 结果:
0 1 99 2 3 0 0 0 0 0
(2)Arrays.copyOf()方法
  • 源码:
    public static int[] copyOf(int[] original, int newLength) {// 申请一个新的数组int[] copy = new int[newLength];// 调用System.arraycopy,将源数组中的数据进行拷贝,并返回新的数组System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));return copy;}
  • 场景:
   /**以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。*/public Object[] toArray() {//elementData:要复制的数组;size:要复制的长度return Arrays.copyOf(elementData, size);}

使用 Arrays.copyOf()方法主要是为了给原有数组扩容,测试代码如下:

public class ArrayscopyOfTest {public static void main(String[] args) {int[] a = new int[3];a[0] = 0;a[1] = 1;a[2] = 2;int[] b = Arrays.copyOf(a, 10);System.out.println("b.length"+b.length);}
}
  • 结果:
10
(3)两者联系和区别
  • 联系:Arrays.copyOf()内部实际调用了 System.arraycopy() 方法
  • 区别:
    • System.arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组
    • 而且可以选择拷贝的起点和长度以及放入新数组中的位置
    • Arrays.copyOf() 是系统自动在内部新建一个数组,并返回该数组。

3.3.4分析ensureCapacity()方法

ArrayList 源码中有一个 ensureCapacity 方法,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢?

    /**如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。** @param   minCapacity   所需的最小容量*/public void ensureCapacity(int minCapacity) {int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)// any size if not default element table? 0// larger than default for default empty table. It's already// supposed to be at default size.: DEFAULT_CAPACITY;if (minCapacity > minExpand) {ensureExplicitCapacity(minCapacity);}}

理论上来说,最好在向 ArrayList 添加大量元素之前用 ensureCapacity 方法,以减少增量重新分配的次数

  • 测试1:
public class EnsureCapacityTest {public static void main(String[] args) {ArrayList<Object> list = new ArrayList<Object>();final int N = 10000000;long startTime = System.currentTimeMillis();for (int i = 0; i < N; i++) {list.add(i);}long endTime = System.currentTimeMillis();System.out.println("使用ensureCapacity方法前:"+(endTime - startTime));}
}
  • 运行结果:
使用ensureCapacity方法前:2158
  • 测试2:
public class EnsureCapacityTest {public static void main(String[] args) {ArrayList<Object> list = new ArrayList<Object>();final int N = 10000000;long startTime1 = System.currentTimeMillis();list.ensureCapacity(N);for (int i = 0; i < N; i++) {list.add(i);}long endTime1 = System.currentTimeMillis();System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1));}
}
  • 运行结果:
使用ensureCapacity方法后:1773

通过运行结果,我们可以看出向 ArrayList 添加大量元素之前使用ensureCapacity 方法可以提升性能。不过,这个性能差距几乎可以忽略不计。而且,实际项目根本也不可能往 ArrayList 里面添加这么多元素。

3.4ArrayList总结

3.4.1ArrayList特点

  1. 存储的对象:存储不唯一、有序的对象,对象只能为引用类型,不能为基本数据类型
  2. 底层数据结构:动态数组,可自动扩容
  3. null值问题:可以存储null值,但没意义
  4. 线程安全问题:不安全
  5. 快速访问问题:由于实现了RandomAccess接口,所以支持通过下标实现快速访问
  6. 补充:支持深浅拷贝、支持序列化

3.4.2ArrayList的存储、扩容机制

  1. ArrayList的默认初始化容量为10,初始化ArrayList对象赋值的是一个空数组,当添加第一个元素时,才将数组扩容为10
  2. ArrayList扩容时,新数组的容量为旧数组的1.5倍(若扩容后的新数组容量仍小于minCapacity,则直接使用minCapacity作为新数组容量)
  3. 新旧数组怎么完成元素转移:Arrays.copyOf(elementData, newCapacity); elementData为旧数组对象,newCapacity为新数组容量; 底层调用了System.arraycopy()方法,该方法可以指定旧数组和新数组的起始位置、以及容量

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/752387.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

react03

react03 修改脚手架创建的打包命令 根据scripts中的命令&#xff0c;执行npm run eject ,输入y, 如果对原始的脚手架文件有过改动需要进行将修改后的文件提交到git 历史区 &#xff0c;防止暴露后的代码覆盖我们自己的文件 git 提交&#xff1a; git add . git commit -m ‘…

java----网络编程(一)

一.什么是网络编程 用户在浏览器中&#xff0c;打开在线视频网站&#xff0c;如优酷看视频&#xff0c;实质是通过网络&#xff0c;获取到网络上的一个视频资源。 与本地打开视频文件类似&#xff0c;只是视频文件这个资源的来源是网络。所谓网络资源就是网络中获取数据。而所…

LabVIEW提升舱救援通讯监测系统

LabVIEW提升舱救援通讯监测系统 随着科技的进步&#xff0c;煤矿救援工作面临着许多新的挑战。为了提高救援效率和安全性&#xff0c;设计并实现了一套基于LabVIEW的提升舱救援通讯监测系统。该系统能够实时监控提升舱内的环境参数和视频图像&#xff0c;确保救援人员和被困人…

el-input设置max、min无效的解决方案

目录 一、方式1&#xff1a;type“number” 二、方式2&#xff1a;oninput&#xff08;推荐&#xff09; 三、计算属性 如下表所示&#xff0c;下面为官方关于max&#xff0c;min的介绍&#xff1a; el-input&#xff1a; max原生属性&#xff0c;设置最大值min原生属性&a…

06.共享内存

1.内存映射&#xff08;mmap&#xff09; 我们在单片机中首先接触到了映射的概念 将一个寄存器的地址映射到了另外的一个存储空间中 内存映射: 内存映射&#xff08;Memory Mapping&#xff09;是一种在计算机科学中使用的技术&#xff0c;它允许将文件或其他设备的内容映射…

idea warning:java源值已过时将在未来所有发行版中删除

在idea中运行maven项目 如果出现idea warning:java源值已过时将在未来所有发行版中删除&#xff0c;详见如下截图所示&#xff1a; 注意&#xff1a;jdk8 要解决这个警告需要设置3个地方 首先打开File->Project Structure中的Project&#xff0c;将SDK和language level都设…

五、保持长期高效的七个法则(二)Rules for Staying Productive Long-Term(1)

For instance - lets say youre a writer.You have a bunch of tasks on your plate for the day, but all of a sudden you get a really good idea for an essay. You should probably start writing now or youll lose your train of thought.What should you do? 举例来说…

分布式搜索引擎(3)

1.数据聚合 **[聚合&#xff08;](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html)[aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html)[&#xff09;](https://www.ela…

机器学习(26)回顾gan+文献阅读

文章目录 摘要Abstract一、李宏毅机器学习——GAN1. Introduce1.1 Network as Generator1.2 Why distribution 2. Generative Adversarial Network2.1 Unconditional generation2.2 Basic idea of GAN 二、文献阅读1. 题目2. abstract3. 网络架构3.1 Theoretical Results 4. 文…

Oracle P6 Professional 配置连接数据库总结

前言 P6 Professional作为Oracle P6计划管理系统的重要套件之一&#xff0c;其操作出色&#xff0c;体检佳&#xff0c;是非常多的计划工程师跟踪项目进度计划的辅助工具。自20年前&#xff0c;Professional一直在不断的演变更新&#xff0c;以适应当前的新技术&#xff0c;从…

【MySQL】MySQL事务

文章目录 一、CURD不加控制&#xff0c;会有什么问题&#xff1f;二、事务的概念三、事务出现的原因四、事务的版本支持五、事务提交方式六、事务常见操作方式七、事务隔离级别1.理解隔离性12.隔离级别3.查看与设置隔离性4.读未提交【Read Uncommitted】5.读提交【Read Committ…

【Numpy】练习题100道(76-100题完结)

&#x1f33b;个人主页&#xff1a;相洋同学 &#x1f947;学习在于行动、总结和坚持&#xff0c;共勉&#xff01; #学习笔记# Git-hub链接 题目列表&#xff08;题解往下翻&#xff09; 76.考虑一个一维数组Z&#xff0c;构建一个二维数组&#xff0c;其第一行为(Z[0],Z[…

【鸿蒙HarmonyOS开发笔记】组件编程技巧之使用@Builder装饰器实现UI结构复用

概述 当页面有多个相同的UI结构时&#xff0c;若每个都单独声明&#xff0c;同样会有大量重复的代码。为避免重复代码&#xff0c;可以将相同的UI结构提炼为一个自定义组件&#xff0c;完成UI结构的复用。 除此之外&#xff0c;ArkTS还提供了一种更轻量的UI结构复用机制Build…

小白DB补全计划Day1-LeetCode:SQL基本操作select

前言&#xff1a;找工作&#xff08;主人&#xff09;的任务罢了 链接&#xff1a;1757. 可回收且低脂的产品 - 力扣&#xff08;LeetCode&#xff09; 584. 寻找用户推荐人 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 对DB篇的SQL章不太知道怎么写…

数学建模-估计出租车的总数

文章目录 1、随机抽取的号码在总体的排序 1、随机抽取的号码在总体的排序 10个号码从小到大重新排列 [ x 0 , x ] [x_0, x] [x0​,x] 区间内全部整数值 ~ 总体 x 1 , x 2 , … , x 10 总体的一个样本 x_1, x_2, … , x_{10} ~ 总体的一个样本 x1​,x2​,…,x10​ 总体的一个样…

mysql与redis数据测试

题目要求 1.新建一张user表&#xff0c;在表内插入10000条数据。 2.①通过jdbc查询这10000条数据&#xff0c;记录查询时间。 ②通过redis查询这10000条数据&#xff0c;记录查询时间。 3.再次查询这一万条数据&#xff0c;要求根据年龄进行排序&#xff0c;mysql和redis各实现…

【FPGA/IC】什么是模块化设计?

什么是模块化设计 FPGA/IC设计中根据模块层次的不同有两种基本的设计方法&#xff1a; 自下而上方法对设计进行逐次划分的过程是从基本单元出发的&#xff0c;设计树最末枝上的单元是已经设计好的基本单元&#xff0c;或者其他项目开发好的单元或者IP。该方法先对底层的功能块…

探索发布-订阅模式的深度奥秘-实现高效、解耦的系统通信

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自&#xff1a;探索设计模式的魅力&#xff1a;探索发布-订阅模式的深度奥秘-…

Jest:JavaScript的单元测试利器

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

作品展示ETL

1、ETL 作业定义、作业导入、控件拖拽、执行、监控、稽核、告警、报告导出、定时设定 欧洲某国电信系统数据割接作业定义中文页面&#xff08;作业顶层&#xff0c;可切英文&#xff0c;按F1弹当前页面帮助&#xff09; 涉及文件拆分、文件到mysql、库到库、数据清洗、数据转…