图解java集合系列之ArrayList

1.前言

  • 本文基于jdk1.8;
  • 时间复杂度的描述采用大O表示法;

 

2.参考代码

 List<Integer> list = new ArrayList<Integer>(10);
 for (int i = 1; i <= 10; i++) { 
    list.add(i); 
 }
 list.add(11);
 list.add(3, 12);
 Integer num = list.get(3);
 boolean contains = list.contains(4);
 list.set(3, 13);
 list.remove(3);
 list.remove((Integer)4);

 

3.图解

3.1 构造

ArrayList采用数组方式实现,可在构造时指定初始数组长度,如未设置则在添加元素时默认创建长度为10的数组,初始数组长度不够时会自动进行扩容。为节省扩容的开销,当能够确定填充元素的数量时,推荐使用如下构造函数:

  • public ArrayList(int initialCapacity);

 

3.2 添加元素

常用的添加方式有两种:

  •   顺序往后添加:boolean add(E e);
  •   指定下标添加:void add(int index, E element);

3.2.1 顺序添加

顺序添加的查找位置、添加元素的复杂度均为O(1)。

 

3.2.2 扩容

 

3.2.3 指定下标添加

指定下标添加会把指定位置及以后的整段数组复制到后一位,添加时查找的时间复杂度是O(1), 复制数组的时间复杂度是O(n)。往越靠前的位置插入元素复制的数组长度越大,大量使用arrayList.add(0, element)是十分糟糕的做法。

 

3.3 查询元素

常用的list查询方式有:

  •   按下标查询:E get(int index);​​​​
  •   判断是否包含:boolean contains(Object o);

3.3.1 按下标查询

在知道下标的情况下,查询的时间复杂度是O(1),由于这一特性,ArrayList非常适合随机访问。

 

3.3.2 判断是否包含

判断包含从下标0开始遍历数组进行匹配,查询的时间复杂度为O(n)。

 

3.4 指定位置存储元素

指定位置存储元素由于直接指定下标赋值,时间复杂度为O(1);

 

3.5 删除元素

删除元素一般有两种方式:

  •   按下标删除:public E remove(int index);
  •   遍历数组删除:public boolean remove(Object o) ;

3.5.1 按下标删除

按下标删除会把指定位置以后的整段数组复制到前一位,然后把最后一位置空,查找的时间复杂度为O(1), 复制数组的时间

复杂度为O(n);

 

3.5.2 遍历数组删除

遍历数组删除从下标0开始查找匹配元素,匹配到元素后就把这个元素之后的数组复制到前一位,把最后一位置空,遍历删除查找元素的时间复杂度是O(n),复制数组的时间复杂度也是O(n), 由于多了一个查找的步骤,遍历数组删除的效率要低于按下标删除的效率。

 

3.5.3 循环删除

实际业务中,可能会需要在循环中按匹配条件删除元素,而由前面删除元素的讲解可知:删除元素后会导致数组下标的变化。

所以在循环删除时要注意下标的调整,如下所示:

List<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 2, 2, 3, 3, 4, 3, 3));
for (int i = 0; i < list.size(); i++) {
         if(((Integer)3).equals(list.get(i))) {
         list.remove(i);
         i--; //一定要加上这句调整下标,否则连续满足条件时会有遗漏
     }
}

由于删除时,是被删除元素后面的数组往前移动,所以反向删除可以不用调整下标:

List<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 2, 2, 3, 3, 4, 3, 3));
for (int i = list.size() - 1; i >= 0; i--) {
      if(((Integer)3).equals(list.get(i))) {
           list.remove(i);
       }
}

在jdk1.8以前,Iterator删除方式是ArrayList循环删除最常见的用法:

 List<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 2, 2, 3, 3, 4, 3, 3));
 Iterator<Integer> iterator = list.iterator();
 while (iterator.hasNext()) {
      Integer next = iterator.next();
      if(((Integer)3).equals(next)) {
           iterator.remove();
       }
 }

由于ArrayList删除单个元素的时间复杂度是O(n), 以上循环删除元素的方式时间复杂度均为O(n^2) , 当数组比较长且满足删除条件的元素较多时,以上的实现方式效率就堪忧了。从jdk1.8开始,removeIf成了循环删除的推荐方式:

  List<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 2, 2, 3, 3, 4, 3, 3));
  list.removeIf(((Integer) 3)::equals);

removeIf的默认实现是在Collection类中,采用Iterator删除方式, 而ArrayList中重写了这个方法,先记录匹配到的元素位置到

BitSet中,然后重新计算位置,成功把时间复杂度降到O(n),使用时也简洁了许多,jdk1.8以前的环境也可以参考这个思路自己编写一个高效循环删除list元素的工具方法。

 

4 总结

4.1 ArrayList高效函数(时间复杂度O(1))

  • boolean add(E e);
  • E get(int index);​​​​
  • E set(int index, E element);

4.2 ArrayList常用优化方案

  • 如果事先能够估算ArrayList需要的长度,可在构造时指定初始数组长度,节省扩容开销
  • 频繁调用void add(int index, E element)函数且指定下标位置靠前时,考虑转换为LinkedList
  • 调用boolean contains(Object o) 函数比较频繁时,可以考虑把元素放入HashSet里进行查询

4.3 注意事项

  • 删除元素时需注意下标的变化
  • 慎用循环删除,jdk1.8以后尽量采用removeIf进行循环删除,jdk1.8以前可参考removeIf实现高效循环删除
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值