深入理解之源码剖析ArrayList

一、概述

java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。

  • ArrayList知识点:
    • 底层实现是数组。
    • ArrayList无参构造的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍。
    • 在增删时候,需要数组的拷贝复制(数组的复制调用系统的navite方法,由C/C++实现)。
    • 删除元素后容量不会自动减少,若希望减少容量则调用trimToSize()。
    • 它不是线程安全的。它能存放null值。

二、源码剖析ArrayList

  • 对象实现:底层实现是数组(注意注释)。
    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;
    /**
    * 由new Object[initialCapacity]可知,底层创建的是一个数组。
    * 而且有指定长度时根据指定创建,为0时直接返回一个为空的数组,长度为0
    */
    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);
        }
    }
    
    /**
     * Constructs an empty list with an initial capacity of ten.
     *  没有指定长度时:初始容量为10,但没有元素。
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
  • 扩容:ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍。
    • 从add加入元素开始查看。
      • ensureCapacityInternal确认list容量,尝试容量加1,看看有无必要
      • 添加元素
      public boolean add(E e) {
          ensureCapacityInternal(size + 1);  // Increments modCount!!
          elementData[size++] = e;
          return true;
      }
      
    • 如果需要要的最小容量比数组的长度要大,则调用grow()来扩容。最小容量指的是(当数组长度为10,要添加第11个元素时,至少增加一个长度就是11,所以最小容量为11,前面还有一步是将旧的元素个数加上新增的元素个数得到最小容量minCapacity的计算)
      private void ensureExplicitCapacity(int minCapacity) {
          modCount++;
          // overflow-conscious code
          if (minCapacity - elementData.length > 0)
              grow(minCapacity);
      }
      
    • int newCapacity = oldCapacity + (oldCapacity >> 1);这句就是相当于容量加1.5倍,oldCapacity >> 1就是除于2嘛!!!
    • 知识点:这里用到位移运算而不用乘2,原因是cpu本来是不支持乘法运算的,所有的乘法最后还需要转化为位移运算,使用位移运算是为了提高cpu的运行效率。
      private void grow(int minCapacity) {
          // overflow-conscious code
          int oldCapacity = elementData.length;
          int newCapacity = oldCapacity + (oldCapacity >> 1);
          if (newCapacity - minCapacity < 0)
              newCapacity = minCapacity;
          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);
      }
      
  • 扩容后修改数据:在增删时候,需要数组的拷贝复制(数组的复制调用系统的navite方法,本地方法由C/C++实现)。
    • elementData = Arrays.copyOf(elementData, newCapacity);扩容的底层同样是根据最新的容量长度创建一个新的数组,并将旧的数组通过调用系统本地的复制方法将数据复制到新的数组(新的ArrayList)。
      public static native void arraycopy(Object src,  int  srcPos,Object dest, int destPos, int length);
      
  • 不扩容添加数据:直接在数组后面加入即可。
  • 指定索引增/删元素
    • 索引增:主要是将指定索引开始的元素往后复制到数组中。
      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++;
      }
      
    • 索引减:主要是将删除元素的后面的元素复制上前一位填补空缺,最后一用null填补上去。
      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; // clear to let GC do its work
      
          return oldValue;
      }
      
    • 删除元素后容量不会自动减少(因为它使用了null填充了),若希望减少容量则调用trimToSize()。
  • 解决线程不同步
    • 在要求非同步的情况下,我们一般都是使用ArrayList来替代Vector的了。如果想要ArrayList实现同步,在创建时可以使用Collections的方法:List list = Collections.synchronizedList(new ArrayList(…));,就可以实现同步了。

三、API快速使用

  • 创建
    • ArrayList arr = new ArrayList(); 无参构造
    • List list = Collections.synchronizedList(new ArrayList(…)); 线程同步化构造
  • 增:add(数据);(arr.add(索引号,元素);在该位置插入元素,改元素后面的值往后面挪动一个位置。)
  • 删:remove(索引号),注意:使用remove(Object o) 时,自定义类型时需要重写equals和hashCode方法;
  • 查:get(索引号);返回元素值。
  • 改:set(索引号,元素)。
  • 长度:size()返回int长度值。
  • 排序:sort(),传递的Comparator与Comparable自行查阅(如实现Comparable接口后,重写compareTo()方法)。
  • 遍历
    • 使用获取迭代器:it = iterator()方法获取迭代器。
      • it.next获取元素。
    • 使用增强for循环:for(T parameter,arr)

后期笔记

踩坑一:不要一定认为集合操作比数组快。

在实际的应用中,有时候盲目的使用集合也会导致效率低。当数据需要不断的删除、修改时,底层实际上是在不断的创建数组,复制数组的操作,在大数据量时非常影响效率,在数据量小时增删其实ArrayList还是可以的。

踩坑二:在大数据存储时,一般如果数据的大小基本确定,在创建集合时可以指定集合的大小(创建时指定容量的好处)。

当指定了一定的大小后,在存储数据时就可以不用再次扩容来创建更大的集合存储,指定了足够大的初始容量可以减少一些反复创建的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值