数据结构与集合之(1)ArrayList 与 Arrays

    数据结构是指逻辑意义上的数据组织方式及其处理方式。
    从 直接前驱 和 直接后继 个数的维度来看,大体可以将数据结构分为以下四类:
(1)线性结构
    0 至 1 个直接前驱 和 直接后继。线性结构包括 顺序表、链表、栈、队列等。
(2)树结构
    0 至 1 个直接前驱 和 0 至 n 个直接后继(n 大于或等于2 )
(3)图结构
    0 至 n 个直接前驱 和 直接后继(n 大于或等于 2)
(4)哈希结构
    没有直接前驱和后继。哈希结构通过某种特定的哈希函数将索引 和 存储的值关联起来,它是一种查找效率非常高的数据结构。

    Java类集提供了两个重要的接口:Map 和 Collection。
    Collection是所有存放单个元素的集合的最大父接口。它有List(允许重复)和Set(不允许重复)两个重要子接口。
    List 有几个常见实现类:ArrayList [ 动态对象数组 ] ,Vector,,LinkedList [ 链表实现 ]。List 比 Collection 多提供了 get 和 set 方法。


一、ArrayList

1、字段
 private static final long serialVersionUID = 8683452581122892189L;
    //默认容量
    private static final int DEFAULT_CAPACITY = 10;
    //无参构造 或 传入长度为0 时,是空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //存储ArrayList元素的数组缓冲区。
    transient Object[] elementData; 
    // non-private to simplify nested class access

    //数组长度
    private int size;

    //最大数组长度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
2、构造方法

    先来看 ArrayList 的构造方法:

(1)有参构造:

   &#160注意:如果传入了初始容量,是直接按照初始容量构建数组的。·

 public ArrayList(int initialCapacity) {
        //值大于0时,根据构造方法的参数值,忠实地创建一个多大的数组
        if (initialCapacity > 0) {
        //按传入初始长度构造数组
            this.elementData = new Object[initialCapacity];
        } 
        else if (initialCapacity == 0) {
        //如果传入长度为0 ,为空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
(2)无参构造

   &#160注意:如果是无参构造,默认的数组是空数组,(我老记得是默认容量是10 。QAQ )

  /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
    //默认是空数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

   &#160如果是无参构造,开始创建的是个空数组,当调用 add方法,开始添加元素时,首先会检查容量是否够用,这时才把 默认的容量 10 赋给minCapacity ,然后会调用 grow 方法扩容,这时 才扩容成长度为 10 的数组。

3、扩容方法 grow()

    既然是数组,容量不够时就需要扩容,ArrayList扩容机制 1.5倍,Vector扩容后机制 2 倍。

private void grow(int minCapacity) {
        // overflow-conscious code
        
        //获取数组长度赋给 oldCapacity
        int oldCapacity = elementData.length;

       //新容量=原数组长度的 1.5 倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);

//如果新容量小于传入的参数要求最小容量
        if (newCapacity - minCapacity < 0)
        //这给扩的不够啊,得按传入的参数扩
            newCapacity = minCapacity;
        
//如果新容量大于数组能容纳的最大元素个数
if (newCapacity - MAX_ARRAY_SIZE > 0)

//那么再判断传入的参数是否大于MAX_ARRAY_SIZE,
//如果传入的参数大于MAX_ARRAY_SIZE,那么新容量等于Integer.MAX_VALUE,
//否则等于MAX_ARRAY_SIZE
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

     说人话:ArrayList 的扩容过程是这样的,传入 要求的最小参数 minCapacity ,先获取当前数组的长度,以1.5倍扩容,得到新容量 newCapacity,接下来,如果新容量比 要求的最小的还小,那说明扩容得不够,以要求的最小的为准,即 将要求的最小的容量赋给 新容量。接下来要检查新容量有没有超过 允许的数组的最大容量——int 整型的最大值-8(为什么要减 8 呢?等会回答。) 如果超过了,可能是因为扩容过头了,(毕竟是 1.5倍扩容,可能人家传入的刚好是长度的1.1 呢,本身的oldCapacity不够,扩了个 1.5 又太多了),那就看看要求的最小的容量是否大于 允许的最大容量,也就是… … -8,如果大于,就把整数最大值 (2^ 31-1)赋给 新容量;如果不大于,说明确实扩容过头了,把 … .-8赋给新容量。
    之前老看网上说 ArrayList 的最大容量是 2^31-8 ,可是看上面的源码,扩容时候,如果要求的最小容量 已经比 …-8 大了,那就应该把 newCapacity 赋为整数的最大值(因为用来衡量数组的长度是用整数),grow 方法的最后一步是按 调用 Arrays 的 copyOf,把原来的数组元素都拷贝到扩容后的新数组里去,所以我觉得 ArrayList 的最大容量是 2 ^ 31,来看 MAX_ARRAY_SIZE 字段上的注释:

 /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    有道翻译:要分配的数组的最大大小。 有些虚拟机在一个数组中保留一些头 header。试图分配更大的数组可能会导致 OutOfMemoryError:请求的数组大小超过VM限制。所以在不超过 …-8 的前提下,应当是以 … -8 为准的,而超过了,那就只能把 MAX-VALUE 作为数组的容量啦。
    假如需要将 1000 个元素放置在 ArrayList 中,采用默认构造方法,则需要被动扩容 13 次才可以完成存储。反之,如果在初始化时便指定了容量 new ArrayLIst(100), 那么在初始化 ArrayList 对象的时候,就直接分配 1000 个存储空间,从而避免被动扩容 和 数组复制的额外开销。

4、add方法
  • 在末尾添加
 public boolean add(E e) {

//先确保数组容量是否够用
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
 private void ensureCapacityInternal(int minCapacity) {
//如果是无参构造建立的空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

//会按 默认容量10 和 要求最小的容量的值 中 最大值,作为容量最小值,
//所以默认的无参构造是在这一步才
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code

//还是以无参构造为例,这时,minCapacity为10 ,大于 0,需要扩容
        if (minCapacity - elementData.length > 0)

//这时才扩容成大小为 10 的数组
            grow(minCapacity);
    } 
👀 ArrayList 的 add 方法线程不安全举例

(1)
elementData[size++] = e; 分两步执行:elementData [size]=e 和 size++。
假设 size =9,当前的长度是默认的容量 10,先检查数组容量是否够用——无需扩容,接下来,CPU 先给线程 A ,线程A 将 e1 值赋给 elementData[9] ,线程A交出 CPU ,线程 B 获取 CPU ,这时 size 仍为 9 ,线程B 会给 elementData[9] 赋值 e2,相当于覆盖了线程 A 的值,然后 CPU 给线程 A ,size++ 变为10,CPU再给 线程 B ,size++ 变为 11,但是 size [10] 的值却是 null 的。

(2)
假设 size=9,线程A ,先检查数组容量是否够用——无需扩容,CPU 给线程B ,——也是 无需扩容。接下来 CPU 给线程 A,添加元素 ,size变成10 ,A 线程结束,CPU 给 B 线程,因为已经检查过容量,所以 B 线程就会直接给 elementDate [10] 进行赋值,这时发生 数组下标越界异常 ArrayIndexOutOfBoundsException 。

  • 在指定下标处添加
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++;
    }

remove

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);

//将末尾赋为 null,并将 size-1
        elementData[--vsize] = null; // clear to let GC do its work

        return oldValue;
    }

    ❓ArrayList 不是线程安全的,怎么办❓
(1)用 concurrent 包提供的工具将 ArrayList 变为线程安全的。
如:

List<String> list1=Collections.synchronizedList(new ArrayList<String>());

    要注意用户需要手动加锁 (当通过 Iterator 、spliterator 或Stream 进行遍历时)。
(Vector也是需要用户手动加锁的。)
如:

synchronized(list1){Iterator i=list1.iterator();
while(i.hasNext())
{Systrm.out.println(i.next);}
} 

(2)用CopyOnWriteArrayList [COW:写入时复制 容器]。增删改查时都会加锁,都会创建一个新数组,操作完成后再赋给原来的引用,而读操作是不需要锁的。


5、ArrayList 的 iterator 删除元素

    Collection 就实现了一个接口 Iterable ,这个接口有以下方法:

//它会返回 Iterator(是个接口) 的实现类类型,Itr 类代码请稍候
public Iterator<E> iterator() 
    //在 ArrayList 中的实现:
    {
        return new Itr();
    }
default void forEach(Consumer<? super T> action)
default Spliterator<T> spliterator() 

再总结一下:
Iterator 接口是由 Collection 接口支持的”:
    Collection 接口只实现了一个接口,那就是 Iterable 接口,而Iterable 中有三个方法:iterator(),Foreach(),spliterator()。
    所以所有实现了 Collection 接口的子类(各种 List、Set)都会覆写 iterator 方法(像 ArrayList,是有个内部类 Itr implements Iterator ,调用iterator()方法时返回 new Itr(); 像LinkedList,它的父类——一个抽象类AbstractSequentialList 覆写了 iterator()方法,返回的是一个 ListIterator 对象;
像 HashSet ,它的底层是 HashMap ,调用 iterator() 方法时是调用 map.keySet 的iterator 方法,Set 接口实现了 Collection 接口,它的实现类也是要覆写 iterator()方法的。)
所以所有实现了 Collection 接口的子类都可以用 foreach。

“ListIterator 接口是由 List 接口支持的”
    List接口中定义的 listIterator() 方法,这样所有实现了 List 接口的子类都需要覆写 listiterator 方法。


    
    所以 所有的 Collection 接口的实现类肯定都覆写了 iterator 方法,通过迭代输出的形式删除内容:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TestDemo {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("Hello");
        list.add("Hello");
        list.add("B");
        list.add("Bit");
        list.add("Bit");
        Iterator<String> iterator=list.iterator();
        while (iterator.hasNext())
        {
            String str=iterator.next();

            if(str.equals("B"))
            {  iterator.remove();
            continue;}

        System.out.println(str);}
    }
}

     注意到,想删除一个元素,必须先跳过该元素,next 和 remove 方法是互相依赖的。
     来看看 next 和 remove 的源码:

//ArrayList 有个内部类 Itr 实现了 Iterator 接口
private class Itr implements Iterator<E> {


        int cursor;       // index of next element to return

        //上一个元素的下标
        int lastRet = -1; // index of last element returned; -1 if no such
        
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

  public E next() {
            //快速失败机制
            checkForComodification();
            //指针,需要返回的元素的下标
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();


            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
 final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
 public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
            
           //this:在内部类的某个方法中 指定某个嵌套层次的 外围类的 "this"
           //调用 ArrayList 的 remove 方法,即把下标以后的元素依次向前移一位,
           //末尾赋 null
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            }
             catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

    如果先调用 remove ,指向前一个元素的指针 lastRet 是指向 -1 的,数组下标越界,所以必须结合 next() ,先让 cursor、lastRet 都往后挪一个,然后调用 remove 才能删除 lastRet 也就是这时候的[0],删除元素后,再把 cursor、lastRet 挪回原位——0,-1。
在这里插入图片描述
总结:
     调用 remove 时 cursor 会往前指一个,因为删除掉当前的元素,它后面的所有元素会依次往前覆盖,然后末尾赋null,(正是通过ArrayList的remove(int index)方法实现的,ArrayList.this 表示外部类的对象,因为是在内部类Itr中调用这个方法。(类名.this:在内部类方法中指定某个嵌套层次的外围类的“this”)
     所以 cursor 需要前指一个保持是最新的next。而且 lastRet会变为 -1,因此每次 remove 前都需要调用 next 方法 ,才能通过 next 方法中的 cursor 找到当前的 next ,然后将该值赋给 lastRet ,lastRet 不为-1,才不会出现数组下标越界,而抛出 ArrayIndexOutOfBoundsException,才能正确删除元素。 所以如果用 listIterator 可以从前往后 也可以从后向前遍历,需要先从头往前,才能从后向前遍历,正是因为 cursor 和 lastRet 这两个下标。
     

ArrayList 和 LinkedList 区别

(1).是否保证线程安全:
     ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
(2)底层数据结构:
    ArrayList 的底层是 Object数组,可以实现动态扩容,每次扩容成原来的 1.5 倍,支持随机访问,但是删除或者插入元素效率很低,例如在数组中间插入(删除)一个元素,必须把这个元素以后的元素全部向后(前)搬移一个位置。
    LinkedList 底层使用的是双向链表数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。不存在初始容量和扩容的概念。访问一个元素要遍历节点,时间复杂度为 O(n)。删除或者插入一个给定指针指向的结点时间复杂度为 O(1)。但是 LinkedList 的remove(Object o) 方法时间复杂度是 O(n^2) 的,因为要先遍历找到删除的节点。
(3)内存空间占用:
    ArrayList的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间【8】,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比 ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)

ArrayList 与 Vector
  • 相同之处
        底层都是 Object[ ] 数组,默认初始容量都是 10 ,遍历集合中元素可以调用iterator,foreach,listIterator。
  • 不同之处:
    (1)ArrayList 是 JDK1.2 提出的,Vector是 JDK1.0 提出的。
    (2)ArrayList 非同步,线程不安全,Vector 同步,线程安全。
    (3)在需要遍历集合中元素时,Vector 可以调用 elements 方法,返回值是 Enumetor 类型。
    (4)Vector扩容时可以指定容量增长因子。默认是 2 倍

二、Arrays

    Arrays 是针对数组对象进行操作的工具类,包括数组的排序、查找、对比、拷贝等操作。(尤其是排序,在多个 JDK 版本中不断地进化,比如原来的归并排序改成 Timsort,明显地改善了集合的排序性能。)
    数组 与 集合 都是用来存储对象的容器,前者性质单一,方便易用;后者类型安全,功能强大,且两者之间必然有互相转换的方式。 在数组转集合的过程中,注意是否使用了视图方式直接返回数组中的数据。比如 Arrays.asList() 为例,它把数组转换成集合时,不能使用其 修改集合相关的方法,它的 add / remove / clear 方法会抛出 UnsupportedOperationException 异常。
👀

import java.util.Arrays;
import java.util.List;

public class ArrayAsList {
    public static void main(String[] args) {
        String[] stringArray=new String[3];
        stringArray[0]="one";
        stringArray[1]="two";
        stringArray[2]="three";

        List<String> stringList= Arrays.asList(stringArray);
        //修改转换后的集合,将第一个元素“one”改成“oneLIst”
        stringList.set(0,"oneList");
        System.out.println(stringArray[0]);

        stringList.add("four");
        stringList.remove(2);
        stringList.clear();
    }
}

运行结果:
在这里插入图片描述
     可以通过 set() 方法修改元素的值,原有数组相应位置的值同时也会被修改,但是不能进行修改元素个数的任何操作,否则 编译正确,但是均会抛出 UnsupportedOperationException 异常。 Arrays.asList 体现的是适配器模式,后台的数据仍是原有数组,set() 方法即间接对数组进行值的修改操作。Arrays.asList 返回对象是一个 Arrays 的内部类,它并没有实现集合个数的相关修改方法,这也是抛出异常的原因之一。源码如下:

    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

     返回的明明是 ArrayList 对象,咋就不能随心所欲对集合进行修改呢? 注意 此 ArrayList 非彼 ArrayList,虽然 Arrays 和 ArrayList 同属一个包,但是在 Arrays 类中还定义了一个 ArrayList 的内部类,根据作用域就近原则,此处的 ArrayList 是李鬼😂,即 这是个内部类。此 李鬼十分简单,只提供了个别方法的实现:

在这里插入图片描述

     会抛出 UnsupportedOperationException 异常 ,是因为它的父类 AbstractList:

public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
 public E remove(int index) {
        throw new UnsupportedOperationException();
    }

     好家伙,这货有气节,它传递的信息是 “要么直接用我,要么小心异常!”。数组转集合引发故障还是很常见的,比如,某业务调用某接口时,对方以这样的方式返回一个 List 类型的集合对象,本方获取集合数据时,99.9% 是只读操作,但小概率需要增加一个元素,就会引发故障。所以, 在使用数组转集合时,需要使用 李逵 java.util.ArrayList 直接创建一个新集合,参数就是 Arrays.asList 返回的不可变集合 ,源码如下:

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

    相比于 数组 转 集合,集合 转 数组 更加可控,毕竟是从相对自由的集合容器 转为 更加苛刻的 数组。比如,适配别人的数组接口,或者 进行局部方法计算等,都可能遇到 集合 转 数组的情况。

👀 例:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class LIstToArray {
    public static void main(String[] args) {
        List<String> list=new ArrayList<String>(3);
        list.add("one");
        list.add("two");
        list.add("three");

        //(1)
        Object[] array1=list.toArray();

        //(2)
        String[] array2=new String[2];
        list.toArray(array2);
        System.out.println(Arrays.asList(array2));

        //(3)
        String[] array3=new String[3];
        list.toArray(array3);
        System.out.println(Arrays.asList(array3)); 
    }
}

运行结果:
在这里插入图片描述
     运行成功了,从 (1) 可以看出,list.toArray() 返回的是 Object[ ] ,改代码后发现,不能用 String[ ] 去接,尽管返回数组的每个元素都是 String 类型的,但就是不能用 String[ ] 去接,书上说这样说明泛型丢失不要用 toArray() 无参方法把集合转换成数组。来看看 toArray 方法的源码,就懂了:

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

关键在这,返回的是 Object[ ] 类型的数组 elementData:

  transient Object[] elementData; // non-private to simplify nested class access

(同时也注意到,这个存储 ArrayList 真实数据的数组由 transient 修饰,表示此字段在类的序列化时常被忽略。因为集合序列化时 系统会调用 writeObject 写入流中,在网络客户端反序列化的 readObject 时,会重新赋值到 新对象的 elementData 中。点解多此一举? 因为 elementData 容量经常会大于 实际存储元素的数量,所以只需发送真正有价值的数组元素即可。)

@SuppressWarnings("unchecked")
    public static <T> T[] copyOf(T[] original, int newLength) 
    {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

但是吧,复制值的时候,传进来的是泛型 U[ ] 数组,所以 拷贝的原料 original 里的元素是啥类型,最后结果里的数组的元素也就是啥类型(个人理解,如有异议请及时指出):

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        //新建一个数组 copy
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

     从 (2)可以看出,传入的数组的容量不够,输出值是 null,来看源码:

@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;
    }

     最后来用代码模拟一下 入参数组容量不够时、入参数组容量刚好时,以及 入参数组容量超过集合大小时,大概的执行时间,代码如下:

import java.util.ArrayList;
import java.util.List;

public class ToArraySpeedTest {
    private static final int COUNT=100 * 100 * 100;
    public static void main(String[] args) {
        List<Double> list=new ArrayList<>(COUNT);

        //构造一个 100 万个元素的测试集合
        for(int i=0;i<COUNT;i++)
        {list.add(i * 1.0);}

        long start=System.nanoTime();

        Double[] notEnoughArray=new Double[COUNT-1];
        list.toArray(notEnoughArray);

        long middle1=System.nanoTime();

        Double[] equalArray=new Double[COUNT];
        list.toArray(equalArray);

        long middle2=System.nanoTime();

        Double[] doubleArray=new Double[COUNT * 2];
        list.toArray(doubleArray);
        long end=System.nanoTime();

        long notEnoughArrayTime=middle1-start;
        long equalEnoughArrayTime=middle2-middle1;
        long doubleArrayTime=end-middle2;
        System.out.println("数组容量小于集合大小:notEnoughArrayTime:"+notEnoughArrayTime/
                (1000.0 * 1000.0) + "ms");
        System.out.println("数组容量等于集合大小:equalEnoughArrayTime:"+equalEnoughArrayTime/
                (1000.0 * 1000.0) + "ms");

        System.out.println("数组容量大于集合大小:doubleArrayTime:"+doubleArrayTime/
                (1000.0 * 1000.0) + "ms");
    }
}

运行结果:
数组容量小于集合大小:notEnoughArrayTime:85.5096ms
数组容量等于集合大小:equalEnoughArrayTime:7.1272ms
数组容量大于集合大小:doubleArrayTime:8.7597ms
    
    具体的执行时间,由于 CPU 资源占用的随机性,会有一定差异,多次运行结果显示,当数组容量等于集合大小时,运行总是最快的,空间消耗也是最少的。由此证明,如果数组初始大小设置不当,不仅会降低性能,还会浪费空间。使用集合 toArray(T[ ] array) 方法,转换成数组时,注意需要传入类型完全一样的数组,并且它的容量大小为 list.size( ) 。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值