美文网首页
Java ArrayList源码学习

Java ArrayList源码学习

作者: 梦工厂 | 来源:发表于2018-03-06 16:37 被阅读43次
    ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    

    一 自动扩容机制

    Resizable-array implementation of the List interface.
    capacity: 存储list元素的底层数组的大小;
    list size:存储的list元素的个数;

    1. 初始化
      不指明容量时,底层数组默认是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,即{}
      指明容量为0时,底层数组为EMPTY_ELEMENTDATA,即{}
      指明容量>0时, this.elementData = new Object[initialCapacity];

      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.
      public ArrayList() { 
           this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
      }
      
    2. add

          public boolean add(E e) {
              ensureCapacityInternal(size + 1);  // 确保底层数组的容量
              elementData[size++] = e;
              return true;
          }
      

      先计算需要的容量,然后进行分配;

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

      计算所需的最小容量
      如果开始没有指定容量大小,最低容量设为Math.max(10, minCapacity);
      为什么没有指定容量时,不直接设为10呢?反序列化时也会调用,minCapacity可能>10;
      如果开始制定了容量为0,最低容量是0;

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

      底层数组容量是否改变,均执行modCount++; 表示list结构性变化;

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

      实际的数组扩增函数:1.5倍扩增

          private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
                                         //实际上小于MAX_ARRAY_SIZE,就报错OutOfMemoryError
          /**
           * Increases the capacity to ensure that it can hold at least the
           * number of elements specified by the minimum capacity argument.
           *
           * @param minCapacity the desired minimum capacity
           */
          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);
          }
      

      ArrayList<Integer> list =new ArrayList<Integer>();
      list.add(1); 默认数组分配容量为10

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

      clear函数也并不会调节底层数组的大小;

          /**
           * Removes all of the elements from this list.  The list will
           * be empty after this call returns.
           */
          public void clear() {
              modCount++;
      
              // clear to let GC do its work
              for (int i = 0; i < size; i++)
                  elementData[i] = null;
      
              size = 0;
          }
      

      真正减少底层数组容量的函数调用 trimToSize()

          /**
           * Trims the capacity of this <tt>ArrayList</tt> instance to be the
           * list's current size.  An application can use this operation to minimize
           * the storage of an <tt>ArrayList</tt> instance.
           */
          public void trimToSize() {
              modCount++;
              if (size < elementData.length) {
                  elementData = (size == 0)
                    ? EMPTY_ELEMENTDATA
                    : Arrays.copyOf(elementData, size);
              }
          }
      

    二: 线程不安全

    This class is roughly equivalent to Vector, except that it is unsynchronized.
    推荐方式:List list = Collections.synchronizedList(new ArrayList(...));


    三: Fail-Fast 机制,ConcurrentModificationException

    The iterators returned by this class's iterator methods are fail-fast:
    if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own ListIterator.remove() methods, the iterator will throw a ConcurrentModificationException

    1. A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification. (内部int值modCount负责记录更改次数)

    2. 一旦创建了Iterator后,Iterator会记录当前list的modCount,并保存为expectedModCount;
      此后外部list发生结构性更改,外部modCount更改,而内部expectedModCount不变,再次调用Iterator函数时无法通过检查,报错;

      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;
      
              public boolean hasNext() {
                  return cursor != size;
              }
      
              @SuppressWarnings("unchecked")
              public E next() {
                  checkForComodification();
                  int i = cursor;
                  if (i >= size)
                      throw new NoSuchElementException();
                  Object[] elementData = ArrayList.this.elementData;
                  if (i >= elementData.length)
                      throw new ConcurrentModificationException();
                  cursor = i + 1;
                  return (E) elementData[lastRet = i];
              }
      
              public void remove() {
                  if (lastRet < 0)
                      throw new IllegalStateException();
                  checkForComodification();
      
                  try {
                      ArrayList.this.remove(lastRet);
                      cursor = lastRet;
                      lastRet = -1;
                      expectedModCount = modCount;
                  } catch (IndexOutOfBoundsException ex) {
                      throw new ConcurrentModificationException();
                  }
              }
      
              final void checkForComodification() {
                  if (modCount != expectedModCount)
                      throw new ConcurrentModificationException();
              }
          }
      

      Iterator.remove是删除前一次返回的值;
      Iterator不能连续remove的原因,remove后 lastRet = -1;

    3. 解决方式:
      3.1)Iterator创建之后,不能用ArrayList增删元素。 只能用Iterator删除元素;
      因为Iterator.remove会同步更新list的modCount以及expectedModCount;
      subList 和Iterator 一样,创建后list就不能结构性修改了;list的增删不要Iterator的操作混在一起
      3.2)java1.8后,貌似removeIf函数式编程也可以避免,待研究;
      注意
      ArrayList线程不安全,改为Vector或者Collections.synchronizedList(list);并不能避免ConcurrentModificationException。
      因为线程安全是多线程排队访问list,不同时访问。仍然允许一个创建了Iterator,另一个修改,只要不是同时就没问题。


    四: 总结

    1. ArrayList默认扩增方式为1.5倍;
    2. ArrayList随机访问get,set是0(1)时间开销,add有可能扩容,移动数组元素0(n)时间开销;删除移动数组元素0(n)时间开销;Iterator.remove移动数组元素0(n)时间开销;
      数组优势,数组劣势
    3. list转array
        List<String> v = new ArrayList<>();
        return v.toArray(new String[v.size()]);
      

    @梦工厂 2018.3.6

    相关文章

      网友评论

          本文标题:Java ArrayList源码学习

          本文链接:https://www.haomeiwen.com/subject/kyplfftx.html