《Java编程的逻辑》笔记8: 剖析ArrayList

Part3 泛型与容器

第9章 列表和队列

9.1 剖析ArrayList
  • 基本原理

    ArrayList内部是基于一个动态扩容的数组实现的,其实例变量声明如下:

    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    private int size;
    

    上述elementData用于存储实际数据,size变量记录实际元素的个数,elementData会随着实际元素个数的增多而重新分配。

    我们可以看看ArrayList的几个构造函数源码:

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

    上述构造函数传入一个初始容量,如果这个初始容量大于0,则创建相应大小的数组并赋值给elementData,如果为0,则将其初始化为空数组EMPTY_ELEMENTDATA(用于共享的空数组实例)

      /**
       * Constructs an empty list with an initial capacity of ten.
       */
      public ArrayList() {
          this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
      }
    

    第二个构造函数没有传入容量参数,则初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA默认空数组实例,这个和之前的EMPTY_ELEMENTDATA有什么不同呢?虽然都是空数组实例,但是EMPTY_ELEMENTDATA是用户传入的容量为0时初始化为空,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA是用于默认大小的空实例(为了后续当add第一个元素时,应该分配多少容量)

    add方法源码如下:

      public boolean add(E e) {
          ensureCapacityInternal(size + 1);  // Increments modCount!!
          elementData[size++] = e;
          return true;
      }
    

    首先调用ensureCapacityInternal确保容量是够的,其代码如下:

      private void ensureCapacityInternal(int minCapacity) {
          ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
      }
    

    里面首先调用calculateCapacity函数计算要增加的容量,代码如下:

      private static int calculateCapacity(Object[] elementData, int minCapacity) {
          if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              return Math.max(DEFAULT_CAPACITY, minCapacity);
          }
          return minCapacity;
      }
    

    首先判断elementData是不是默认空实例数组(即第一次添加的时候),是的话分配的大小为DEFAULT_CAPACITYminCapacity(size+1)二者较大值。首次分配的时候minCapacity为1,所以分配的大小为DEFAULT_CAPACITY为10.

    然后调用ensureExplicitCapacity函数,代码如下:

      private void ensureExplicitCapacity(int minCapacity) {
          modCount++;
    
          // overflow-conscious code
          if (minCapacity - elementData.length > 0)
              grow(minCapacity);
      }
    

    里面有个modCount变量,而且每次加1,表示内部的修改次数。grow方法为实际分配容量的代码如下:

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

    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);
          elementData[--size] = null; // clear to let GC do its work
    
          return oldValue;
      }
    

    上述计算要移动的元素个数,从index往后的元素都要往前移动一位,实际调用System.arraycopy方法移动元素,然后elementData[--size] = null;将size减1,并将最后一个元素设为null表示不再引用原来对象,从而可以被垃圾回收器回收。

    get方法源码如下:

      public E get(int index) {
          rangeCheck(index);
    
          return elementData(index);
      }
    

    其调用elementData方法,其代码如下:

      E elementData(int index) {
          return (E) elementData[index];
      }
    

    由于elementData里的元素为Object类型,需要进行强制转换。

  • 迭代:

    (1) foreach循环:

      ArrayList<Integer> intList = new ArrayList<Integer>();
      intList.add(123);
      intList.add(456);
      intList.add(789);
      for(Integer a : intList){
          System.out.println(a);
      }
    

    编译器会将它转换为类似如下代码:

      Iterator<Integer> it = intList.iterator();
      while(it.hasNext()){
          System.out.println(it.next());
      }
    

    ArrayList实现了Iterable接口(实际上是通过实现Collection来继承的),Iterable接口表示可迭代,主要定义如下:

      public interface Iterable<T> {
          Iterator<T> iterator();
      }
    

    就是要求实现iterator方法,返回一个实现了Iterator接口的对象(实际的迭代器),Iterator接口主要定义如下:

      public interface Iterator<E> {
          boolean hasNext();
          E next();
          void remove();
      }
    

    ListIterator: ArrayList除了提供基本的iterator方法外,还提供了两个返回ListIterator接口的方法:

      public ListIterator<E> listIterator()
      public ListIterator<E> listIterator(int index)
    

    ListIterator扩展了Iterator接口,增加了一些方法,向前遍历、添加元素、修改元素、返回索引位置等,添加的方法有:

      public interface ListIterator<E> extends Iterator<E> {
          boolean hasPrevious();
          E previous();
          int nextIndex();
          int previousIndex();
          void set(E e);
          void add(E e);
      }
    

    (2) 迭代器实现原理

    iterator()迭代器:

      public Iterator<E> iterator() {
          return new Itr();
      }
    

    上述方法返回了一个Itr对象,Itr对象的声明如下:

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

    Itr类继承了Iterator接口,实现了其中的hashNextnext, remove等方法,具体如何实现的需要仔细阅读源码。

    listIterator()迭代器:

      public ListIterator<E> listIterator() {
          return new ListItr(0);
      }
    

    其返回的是ListItr对象,其声明如下:

      private class ListItr extends Itr implements ListIterator<E> {
          ...
      }
    

    除了Iterator的方法,还实现了previous, hasPrevious,即可以前向遍历。

    通过上述我们可以看到,我们可以在类中通过继承Iterator等接口实现自定义迭代器类。

    (3) 迭代器的好处

    迭代器语法更为通用,它适用于各种容器类。

    迭代器表示的是一种关注点分离的思想,将数据的实际组织方式与数据的迭代遍历相分离,是一种常见的设计模式

    从封装的思路上讲,迭代器封装了各种数据组织方式的迭代操作,提供了简单和一致的接口。

  • ArrayList实现的接口

    ArrayList还实现了三个主要的接口Collection, List和RandomAccess.

    Collection: Collection表示一个数据集合,数据间没有位置或顺序的概念.

    接口定义为:

      public interface Collection<E> extends Iterable<E> {
          int size();
          boolean isEmpty();
          boolean contains(Object o);
          Iterator<E> iterator();
          Object[] toArray();
          <T> T[] toArray(T[] a);
          boolean add(E e);
          boolean remove(Object o);
          boolean containsAll(Collection<?> c);
          boolean addAll(Collection<? extends E> c);
          boolean removeAll(Collection<?> c);
          boolean retainAll(Collection<?> c);
          void clear();
          boolean equals(Object o);
          int hashCode();
      }
    

    List: List表示有顺序或位置的数据集合,它扩展了Collection,增加的主要方法有:

      boolean addAll(int index, Collection<? extends E> c);
      E get(int index);
      E set(int index, E element);
      void add(int index, E element);
      E remove(int index);
      int indexOf(Object o);
      int lastIndexOf(Object o);
      ListIterator<E> listIterator();
      ListIterator<E> listIterator(int index);
      List<E> subList(int fromIndex, int toIndex);
    

    RandomAccess: 其定义为:

      public interface RandomAccess {
      }
    

    没有定义任何代码。这有什么用呢?这种没有任何代码的接口在Java中被称之为标记接口,用于声明类的一种属性。

    实现了RandomAccess接口的类表示可以随机访问,可随机访问就是具备类似数组那样的特性.

参考:
《Java编程的逻辑》第9章 列表和队列

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值