Java源码-集合-ArrayList

基于JDK1.8.0_191

介绍

  在Java中,对于数据的保存和使用有多种方式,主要的目的是以更少的资源消耗解决更多的问题,数组就是其中的一种,它的特点是所有的数据都保存在内存的一段连续空间中,使它能更容易的进行数据的修改和查找。
而ArrayList就是基于数组的特性,进行一系列封装而得到的一个数据的工具类,它有以下特点:

  • 大小可变(数组的大小并不可变,ArrayList实际上是申请一个新的更大的数组,然后把原来数组的数据拷贝到新数组当中,以此实现扩容)
  • 修改和查找的时间复杂度都为o(1)
  • 允许所有元素,包括null
  • ArrayList是线程不安全的(如果需要使用线程安全的,可以这么写
    List list = Collections.synchronizedList(new ArrayList(...));

构造函数

ArrayList有三个构造函数

  • 第一个,无参构造函数
public ArrayList();

翻译一下就是Object[] elementData = {},创建一个空数组

  • 第二个,设定初始大小
public ArrayList(int initialCapacity);

创建一个initialCapacity大小的数组,建议使用并根据实际当中的情况设置初始大小,因为扩容ArrayList是一个很耗资源的事

  • 第三个,创建包含指定元素的数组
public ArrayList(Collection<? extends E> c);

创建一个和传入的参数一模一样的数组。参数是实现了Collection接口的类,会通过集合的toArray方法转换为数组

部分源码解析

点击查看详细内容

    //新增,放到数组末尾
    public boolean add(E e) {
        //判断是否需要扩容
        ensureCapacityInternal(size + 1);
        //插到末尾
        elementData[size++] = e;
        return true;
    }
    //在指定位置插入
    public void add(int index, E element) {
        //判断index大小是否合法
        rangeCheckForAdd(index);
        //判断是否扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //要插入的位置之后的数据整体后移一位。耗费资源极大,所以不建议频繁插入
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);
        //插入数据
        elementData[index] = element;
        //调整大小
        size++;
    }
    
     //插入集合
    public boolean addAll(Collection extends E> c) {
        //先将集合转换为数组
        Object[] a = c.toArray();
        int numNew = a.length;
        //判断新的大小是否要扩容
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //在数组末尾插入新的数据
        System.arraycopy(a, 0, elementData, size, numNew);
        //重新调整大小
        size += numNew;
        return numNew != 0;
    }
    
    //清空ArrayList
    public void clear() {
        modCount++;
        //将数组的每一位设为空
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }
    
    //扩容判断
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                // 如果不是空数组,可以设置成任意大小的正数值
                ? 0
                //如果是空数组,最小就是10,参数比10还小,是不会扩容的
                : DEFAULT_CAPACITY;
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    
    //jdk 8新增的通过lambda表达式遍历ArrayList
    public void forEach(Consumer super E> action);
    //使用案例
    List list = new ArrayList<>();
    list.add("1");
    list.add("2");
    list.forEach(x -> System.out.println(x));
    
    //最后一次出现指定对象的位置,注意区分NULL
    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;
    }
    
     //移除位置上的对象
    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
        elementData[--size] = null;
        return oldValue;
    }
    
    //删除满足过滤条件的对象,为1.8新出的方法
    public boolean removeIf(Predicate super E> filter);
    //使用案例
    List list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    //删除比1大的
    list.removeIf(x -> x > 1);
    System.out.println(list.toString());
    //输出结果:[1]

    //对数组的每个元素,执行某个操作
    public void replaceAll(UnaryOperator operator);
    //使用案例
    List list = new ArrayList<>();
     list.add(1);
     list.add(2);
     list.add(3);
     //删除比1大的
     list.replaceAll(x -> ++x);
     System.out.println(list.toString());
    //输出结果[2, 3, 4]
    //程序对数组的每个元素都进行了+1的操作

ArrayList的查询

  通过阅读源码我们发现,在源码当中需要使用到遍历整个ArrayList的时候,都是使用的传统for循环,那么可以试一试各个遍历ArrayList的方式有什么不同
1.传统for循环

  List<String> list = new ArrayList<>();
  for(int i = 0; i < 10000000; i++){
    list.add(i + "");
  }
  
  long time1 = System.currentTimeMillis();
  for(int i = 0; i < list.size(); i++){}

  long time2 = System.currentTimeMillis();
  System.out.println(time2 - time1);

输出的结果为5ms

2.简便for循环
把循环方式改为

  for(String str : list){}

输出的结果为40ms

3.Iterator
把循环方式改为

  Iterator<String> iter = list.iterator();
  while(iter.hasNext()){
    iter.next();
  }

输出的结果为7ms

4.foreach
java list.forEach(x -> {});
使用jdk8新出的foreach
输出的结果为77ms

这四种方式差距的还是有些明显的,那么来分析下原因
首先第二种的简便for循环,其实内部使用的还是Iterator方式,我们把第二种代码进行编译,然后查看.class文件可以发现,for(String str : list){}编译后就变成了

  String var5;
  for(Iterator var4 = list.iterator(); var4.hasNext(); var5 = (String)var4.next()) {}

然后我们看第四种,第四种方式的源码为:

  default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
      action.accept(t);
    }
  }

我们可以看出来,其实它内部嵌套的是第二种循环方式,所以耗时更久

总结:使用第一种最好,虽然代码长了一点,但是效率会提高很多

转载于:https://www.cnblogs.com/guodaye/p/11218277.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值