ArrayList的使用详解

ArrayList是顺序表的一种实现,在顺序表的基础上提供了更加丰富的功能。ArrayList继承了AbstractList类,实现了List、RandomAccess、Cloneable、Serializable接口。

ArrayList集合特点

  • 有序,允许为null值,线程不安全。

  • 通过下标可以快速查询集合内元素,利查询,不利增删。

  • 底层实现是基于数组的。

  • 1.5倍动态扩容,jdk1.7默认值为10,1.8之后默认值是0,第一次扩容是10。

ArrayList实现类的常用方法

public class ArrayDemo {
    public static void main(String[] args) {
     /**
      * 创建一个ArrayList对象,
      * 定义元素个数,
      * 初始化Arraylist中的元素
      * */
     ArrayList<Integer> arrayList=new ArrayList<>();
     int size=10;
        for (int i = 0; i <size ; i++) {
            arrayList.add(i);
        }

        //调用有参构造传入ArrayList作为参数
        ArrayList<Integer> otherList = new ArrayList<>(arrayList);
        System.out.println("arrayList的第0个位置元素:" + arrayList.get(0));
        System.out.println("arrayList元素5所在位置:" + arrayList.get(5));
        System.out.println("arrayList是否包含元素10:" + arrayList.contains(10));
        System.out.println("arrayList删除第0个位置元素:" + arrayList.remove(0));

/**
 * 获取ArrayList的迭代器
 */
        Iterator<Integer> iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            Integer element = iterator.next();
            System.out.println("iterator()迭代器输出ArrayList元素:" + element);
            if (element == 5) {
                iterator.remove();//删除元素5
                System.out.println("iterator()迭代器删除ArrayList元素:" + element);
                break;//跳出循环
            }
        }
        iterator.forEachRemaining(integer -> System.out.println("forEachRemaining()输出元素" + integer));
//        删除与ArrayList交集部分
        otherList.removeAll(arrayList);


    }
}

-------------------------------------------------------
/**结果
arrayList的第0个位置元素:0
arrayList元素5所在位置:5
arrayList是否包含元素10:false
arrayList删除第0个位置元素:0
iterator()迭代器输出ArrayList元素:1
iterator()迭代器输出ArrayList元素:2
iterator()迭代器输出ArrayList元素:3
iterator()迭代器输出ArrayList元素:4
iterator()迭代器输出ArrayList元素:5
iterator()迭代器删除ArrayList元素:5
forEachRemaining()输出元素6
forEachRemaining()输出元素7
forEachRemaining()输出元素8
forEachRemaining()输出元素9
*/

ArrayList类的属性

 /**
     * Default initial capacity.
     * 默认初始容量。
     */
    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.
     * 空数组,用于控制当ArrayList加入新元素时,计算扩充的容量
     */
    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 Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     * ArrayList包含的元素个数
     */
    private int size;

ArrayList类是基于数组实现的线性表,并且用size属性记录ArrayList对象中包含的元素的个数,因此获取ArrayList对象的大小的时间复杂度为O(1),因为不需要对ArrayList对象中的每个元素进行遍历。

ArrayList类的构造器

  • 无参构造

ArrayList()://空参构造器,默认容量10;
  • 构造指定初始容量空列表

ArrayList(int initialCapacity)://带参数的构造器,可以指定容量。

    /*
带初始容量的构造器中会对指定的初始容量进行校验。如果初始容量参数非法,就会抛IllegalArgumentException异常。初始容量不宜设置过大,否则可能会造成存储空间浪费,甚至会影响JVM(Java Virtual Machine,Java虚拟机)的性能。 
    */
  • 构造一个包含指定Collection列表

ArrayList(Collection<? extends E> c)://构造一个包含指定Collection中元素的列表,列表中元素存储的顺序跟Collection的迭代器返回元素的顺序一样

常用方法

  • boolean add(E e):将指定的元素追加到列表的结尾;

  • void add(int index, E element):向列表的指定位置插入一个元素。将当前位于该位置的元素(如果有的话)和它的后续元素向右移动,并且将这些向右移动的元素的索引+1;

  • E get(int index):返回列表中指定位置的元素;

  • E set(int index, E element):替换列表中指定位置的元素,返回被替换的元素;

  • E remove(int index):移除列表中指定位置的元素,将该位置元素的后续元素整体向左移动,并将这些元素的索引-1,最后返回被移除的元素;

  • ensureCapacity(int minCapacity):设置容量。虽然ArrayList可以动态扩容,但是扩容是比较耗费性能的,如果能知道放入ArrayList的元素有多少个,可以用此方法设置ArrayList的容量。

  • int size():返回列表中元素的个数。

  • contains():返回bool类型数据,true集合包含该元素,false不包含该元素

核心源码:

/**
 * 默认初始容量
 */
 private static final int DEFAULT_CAPACITY = 10;
/**
 * 空数组,用于空的列表实例
 */
 private static final Object[] EMPTY_ELEMENTDATA = {};
 /**
  * 当我们用无参构造器初始化一个ArrayList对象时,容量是默认容量10,此时用于保存元素的数组还是
  * 空数组,直到真正使用时(比如第一次调用add方法添加元素时),才会初始化一个容量为默认容量的数组。
  * 这个空数组就是容量为默认容量时ArrayList的空数组。把它和EMPTY_ELEMENTDATA数组区分开来,以
  * 知道在添加第一个元素时容量需要增加多少。
  */
 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 /**
  * ArrayList用来存储元素的数组。ArrayList的容量就是这个数组的长度。
  */
 transient Object[] elementData; // non-private to simplify nested class access
 /**
  * 列表中元素的个数。
  */
 private int size;
 /**
  * 不指定容量时,默认容量是10,但是初始化时并没有创建一个容量为10的数组,而是使用了默认的空数组。
  * 第一次调用add方法添加元素时才会创建一个容量为默认容量的数组
  */
 public ArrayList() {
 	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 }
 /**
  * initialCapacity大于0时直接初始化数组;
  * initialCapacity等于0时用的是EMPTY_ELEMENTDATA,之所以要跟DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  * 区分开来,是为了分清楚主观上就是要让数组容量为0和数组长度会是默认容量10但是还没初始化这两种不
  * 同的情况。后面的很多代码都是根据使用的空数组对象的不同而走不同的业务逻辑
  */
 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);
     }
 }
 /**
  * 将ArrayList的容量修改成列表的当前大小。应用程序可以使用此操作来最小化ArrayList实例的
  * 存储空间。这里又涉及到数据结构的知识了,容量指的是数组的长度,也就是存放线性表的存储
  * 空间的长度;线性表的长度是线性表中数据元素的个数。线性表的长度总是小于等于数组的长度。
  */
 public void trimToSize() {
     modCount++;
     if (size < elementData.length) {
         elementData = (size == 0)
           ? EMPTY_ELEMENTDATA
           : Arrays.copyOf(elementData, size);
     }
 }
 /**
  * 1.minCapacity小于数组当前长度时啥也不干(总不能缩容吧)。
  * 2.数组是空数组(数组容量本该是默认值,但是还没初始化),且minCapacity小于等于默认容量时啥也不干。
  * 3.其他情况下调用此方法都将扩容。
  */
 public void ensureCapacity(int minCapacity) {
     if (minCapacity > elementData.length
         && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
              && minCapacity <= DEFAULT_CAPACITY)) {
         modCount++;
         grow(minCapacity);
     }
 }
 /**
  * 数组长度大于0时要计算扩容多少容量,这里调用了jdk内部工具类ArraysSupport.newLength方法,
  * 这个方法的作用就是保证至少要扩容当前容量的一半,
  * 即在minCapacity - oldCapacity和oldCapacity >> 1之间求最大值,作为新增的容量。
  * 数组当前容量等于0时,在DEFAULT_CAPACITY和minCapacity之间求最大值,作为数组容量。
  */
 private Object[] grow(int minCapacity) {
     int oldCapacity = elementData.length;
     if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
         int newCapacity = ArraysSupport.newLength(oldCapacity,
                 minCapacity - oldCapacity, /* minimum growth */
                 oldCapacity >> 1           /* preferred growth */);
         return elementData = Arrays.copyOf(elementData, newCapacity);
     } else {
         return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
     }
 }
 /**
  * 往列表末尾追加元素。调用的是私有的add方法,s参数传的是元素个数size,
  * 如果size跟数组长度相等就扩容,因为size是列表长度,所以size - 1就是元素
  * 在数组中的index.上面提到过无参的构造器创建的ArrayList默认容量是10,但数组
  * 一开始不会初始化,所以在调用add方法时会初始化一个容量为10的数组,通过跟踪
  * grow方法的代码可以验证此观点
  */
 public boolean add(E e) {
     modCount++;
     add(e, elementData, size);
     return true;
 }
 private void add(E e, Object[] elementData, int s) {
     if (s == elementData.length)
         elementData = grow();
     elementData[s] = e;
     size = s + 1;
 }
 /**
  * 向列表指定位置插入元素。首先进行index的合法性检查。然后判断是否需要扩容,
  * 如果需要扩容则进行扩容相关操作。最后的插入元素的操作其实也比较简单,就是
  * 将从index开始的元素整体往后移动一个位置,通过数组复制实现,然后用新元素覆
  * 盖index处的元素
  */
 public void add(int index, E element) {
     rangeCheckForAdd(index);
     modCount++;
     final int s;
     Object[] elementData;
     if ((s = size) == (elementData = this.elementData).length)
         elementData = grow();
     System.arraycopy(elementData, index,
                      elementData, index + 1,
                      s - index);
     elementData[index] = element;
     size = s + 1;
 }
 /**
  * 获取元素,跟操作数组一样,比较简单,没啥好说的
  */
  public E get(int index) {
      Objects.checkIndex(index, size);
      return elementData(index);
  }
  /**
   * 替换列表中指定位置的元素。先记录下数组中index处的旧元素,覆盖完成后返回旧元素
   */
  public E set(int index, E element) {
      Objects.checkIndex(index, size);
      E oldValue = elementData(index);
      elementData[index] = element;
      return oldValue;
  }
  /**
   * 移除列表中指定位置的元素,并返回被移除的元素
   */
  public E remove(int index) {
      Objects.checkIndex(index, size);
      final Object[] es = elementData;

      @SuppressWarnings("unchecked") E oldValue = (E) es[index];
      fastRemove(es, index);

      return oldValue;
  }
  /**
   * 如果移除的是列表末尾的那个元素,就直接用null填补此位置。
   * 移除其他位置的元素都需要通过数组拷贝来实现元素位置移动
   */
  private void fastRemove(Object[] es, int i) {
      modCount++;
      final int newSize;
      if ((newSize = size - 1) > i)
          System.arraycopy(es, i + 1, es, i, newSize - i);
      es[size = newSize] = null;
  }
  /**
   * 通过遍历数组来找到需要移除的元素,匹配到第一个元素后就跳出循环。
   * 也就是说只会移除匹配到的第一个元素
   */
  public boolean remove(Object o) {
     final Object[] es = elementData;
     final int size = this.size;
     int i = 0;
     found: {
         if (o == null) {
             for (; i < size; i++)
                 if (es[i] == null)
                     break found;
         } else {
             for (; i < size; i++)
                 if (o.equals(es[i]))
                     break found;
         }
         return false;
     }
     fastRemove(es, i);
     return true;
  }

ArrayList类批量方法

  1. addAll(Collection<? extends E> c)——批量将另一个集合中的元素加入ArrayList的末尾

  1. addAll(int index, Collection<? extends E> c)——指定将另一个集合中的元素插入ArrayList指定的位置后

  1. removeAll(Collection<?> c)——删除ArrayList与另一个集合c的交集部分

  1. retainAll(Collection c)互为相反操作

ArrayList类导出数组方法

  1. toArray()方法用于返回一个包含ArrayList中所有元素的数组

  1. toArray(T[] a)方法用于返回一个包含ArrayList中所有元素的指定类型的数组

ArrayList类排序方法sort()

sort()方法根据传入的比较器Comparator调用Arrays.sort()方法进行排序。这里也是策略设计模式的一种运用场景。Comparator是一个函数式接口,JDK1.8对Comparator接口做了修改,在其中加入了很多默认方法,此处只节选了compare()方法,因为compare()方法是Arrays.sort()方法中用到的。Arrays.sort()方法代码如下:

    public static <T> void sort(T[] a, int fromIndex, int toIndex,
                                Comparator<? super T> c) {
        if (c == null) {
            sort(a, fromIndex, toIndex);
        } else {
            rangeCheck(a.length, fromIndex, toIndex);
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, fromIndex, toIndex, c);
            else
                TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
        }
    }

Arrays.sort()方法会使用ComparableTimSort.sort()方法或者TimSort.sort()方法进行排序。这两种排序方法的实现方式类似,不同之处在于,TimSort.sort()使用自定义比较器进行排序,ComparableTimSort.sort()使用元素的自然顺序进行排序。所谓的自然顺序,即实现了Comparable接口的类,如Integer类或者String类等,这些类的对象可以直接进行大小比较,因为没有自定义的比较器也可以对其进行排序。此处使用的TimSort排序算法是一种混合、稳定高效的排序算法。TimSort排序算法是由Tim Peters于2002年实施使用在Python编程语言中的。从2.3版本开始,TimSort一直是Python的标准排序算法。Java中也实现了TimSort算法。

ArrayList的迭代器

public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(4);
        list.add(1);
        list.add(3);
        list.add(6);
        list.add(4);
        Iterator<Integer> iterator = list.iterator();//声明迭代器,
        while(iterator.hasNext()){
            Integer value = iterator.next();
            if(value==4){
                iterator.remove();
            }
        }
        System.out.println(list);
    }

ArrayList重点

  1. ArrayList是基于顺序表实现的容器。

  1. ArrayList存储模型。

  1. ArrayList查找的时间复杂度。

  1. ArrayList迭代器。

  1. ArrayList线程安全问题及与之对应的并发容器。

  1. iterator迭代器声明一次只能使用一次。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值