ArrayList 简解笔记

170 阅读5分钟

注: 拉钩教育大数据训练营java基础笔记 + 自己补充扩展

ArrayList 简介

ArrayList是一个其容量能够动态增长的动态数组。它继承了AbstractList,实现了ListRandomAccess(标注该类可随机访问), Cloneable(可以克隆拷贝), java.io.Serializable(可序列化)。

有关AbstractList

有关深拷贝和浅拷贝

基本的ArrayList,长于随机访问元素,但是在List中间插入和移除元素时较慢。同时,ArrayList的操作不是线程安全的!一般在单线程中才使用ArrayList,而在多线程中一般使用Vector或者CopyOnWriteArrayList

Vector 和 CopyOnWriteArrayList 介绍

源码剖析

基础内容

成员对象

//序列号id 
private static final long serialVersionUID = 8683452581122892189L;

//transient 标注该对象 不能被序列化;ArrayList元素存储该对象中
transient Object[] elementData;

// 集合中的元素数量,区别capacity
private int size;
 
//初始容量capacity大小
private static final int DEFAULT_CAPACITY = 10;

//集合对数组元素的操作次数
protected transient int modCount = 0;

/**
* 如果 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 
* 表示 该对象还从未添加过元素,modCount为0.
* 但elementData == EMPTY_ELEMENTDATA  只说明这个集合元素为空,
* 可能之前有过添加删除元素的操作
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//预留8个大小空间给jvm存储数组对象头里数组长度_length
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

补充:有关transit解析

补充:jvm中对象的存储

构造函数

有3种初始化ArrayList对象方法

//初始集合对象存储元素为空,容量为0.
public ArrayList() {
       this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
   }
 
/* 
* initialCapacity as ic
* ic > 0 则该对象容量为 ic ,即elementData 大小为ic
* ic == 0 用EMPTY_ELEMENTDATA 赋值elementData 标注空集合
* ic < 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);
       }
   }
/**
* 将集合c中的元素拷贝到elementData素组中
* bug-6260652:elementData = c.toArray() 后elementData数组
* 不一定可以存储Object对象,得加个判断处理下。
* 如果赋值后不是Objectd对象数组,得转换下
*/
public ArrayList(Collection<? extends E> c) {
       elementData = c.toArray();
       if ((size = elementData.length) != 0) {
           // defend against c.toArray (incorrectly) not returning Object[]
           // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
           if (elementData.getClass() != Object[].class)
               elementData = Arrays.copyOf(elementData, size, Object[].class);
       } else {
           // replace with empty array.
           this.elementData = EMPTY_ELEMENTDATA;
       }
   }

补充:Arrays.copy的方法 和 Systems.copy


注: 有关Arrays.ArayList.toArray 和 ArrayList.toArray() 需补充

bug-6260652

bug-6260652

官网解释 bug-6260652


元素操作

添加

public boolean add(E e) {
       modCount++;
       add(e, elementData, size);
       return true;
   }
//这个辅助方法是从add(E)方法分离而来的,为了保持方法字节码低于35,这将有助于add(E)方法调用C1编译循环
private void add(E e, Object[] elementData, int s) {
       if (s == elementData.length)
           elementData = grow();
       elementData[s] = e;
       size = s + 1;
   }
   
//将集合c中元素添加到数组末尾,可能需要扩容
public boolean addAll(Collection<? extends E> c) {
       Object[] a = c.toArray();
       modCount++;
       int numNew = a.length;
       if (numNew == 0)
           return false;
       Object[] elementData;
       final int s;
       if (numNew > (elementData = this.elementData).length - (s = size))
           elementData = grow(s + numNew);
       System.arraycopy(a, 0, elementData, s, numNew);
       size = s + numNew;
       return true;
   }
   
//将集合c元素添加到数组指定位置,可能需要扩容
public boolean addAll(int index, Collection<? extends E> c) {
       rangeCheckForAdd(index);

       Object[] a = c.toArray();
       modCount++;
       int numNew = a.length;
       if (numNew == 0)
           return false;
       Object[] elementData;
       final int s;
       if (numNew > (elementData = this.elementData).length - (s = size))
           elementData = grow(s + numNew);

       int numMoved = s - index;
       if (numMoved > 0)
           System.arraycopy(elementData, index,
                            elementData, index + numNew,
                            numMoved);
       System.arraycopy(a, 0, elementData, index, numNew);
       size = s + numNew;
       return true;
   }

补充:即时编译(Just In Time,JIT)

扩容

当元素数量大于等于elementData容量时,就需要为elementData数组扩容。

//方法为不能外部访问扩容
private Object[] grow() {
        return grow(size + 1);
   }

//newCapacity(minCapacity) 取得新数组容量大小
private Object[] grow(int minCapacity) {
       return elementData = Arrays.copyOf(elementData,
                                          newCapacity(minCapacity));
   }
   
//java.lang.Arrays
/*
* 1.创建指定长度的某种类型的数组copy
* 2.调用本地方法将源数组元素拷贝到copy,并返回copy
*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
       @SuppressWarnings("unchecked")
       T[] copy = ((Object)newType == (Object)Object[].class)
           ? (T[]) new Object[newLength]
           : (T[]) Array.newInstance(newType.getComponentType(), newLength);
       System.arraycopy(original, 0, copy, 0,
                        Math.min(original.length, newLength));
       return copy;
   }

上述扩容时确定新数组容量大小数值newCapacity(minCapacity)源码:


/*
* 新容量 as newCapacity 
* 旧容量 as oldCapacity
* 预估的容量参数 as minCapacity
* 1.newCapacity 为 oldCapacity 的1.5倍
* 2.如果 newCapacity  <= minCapacity
* 	2.1  如果elementData是默认的空数组(数组为空之前且未操作过数据) return DEFAULT_CAPACITY 和 minCapacity 最大值(因为可能minCapacity < 0)
*	2.2  判断是否minCapacity < 0 抛异常
* 3. 步骤2为false 
*	3.1 newCapacity < MAX_ARRAY_SIZE 返回 newCapacity
*	3.2 否则 返回 hugeCapacity(minCapacity)(最大容量)
*/
private int newCapacity(int minCapacity) {
       // overflow-conscious code
       int oldCapacity = elementData.length;
       int newCapacity = oldCapacity + (oldCapacity >> 1);
       if (newCapacity - minCapacity <= 0) {
           if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
               return Math.max(DEFAULT_CAPACITY, minCapacity);
           if (minCapacity < 0) // overflow
               throw new OutOfMemoryError();
           return minCapacity;
       }
       return (newCapacity - MAX_ARRAY_SIZE <= 0)
           ? newCapacity
           : hugeCapacity(minCapacity);
   }

   /*
   * min 大于  MAX_ARRAY_SIZE 不是返回min 而是直接返回整型最大值Integer.MAX_VALUE
   */
   private static int hugeCapacity(int minCapacity) {
       if (minCapacity < 0) // overflow
           throw new OutOfMemoryError();
       return (minCapacity > MAX_ARRAY_SIZE)
           ? Integer.MAX_VALUE
           : MAX_ARRAY_SIZE;
   }

我发现只有当数组扩容时才会判断数组容量大小minCapacity会不会超过MAX_ARRAY_SIZE,而在初始化阶段并没有检测语句;如果我在初始化ArrayList时直接将容量写为MAX_ARRAY_SIZE 或 MAX_ARRAY_SIZE/2 都超出内存异常.

测试代码:

 List<Integer> list = new ArrayList<>((Integer.MAX_VALUE- 8)/2);
       System.out.println(list.size());

测试结果:

补充:jvmn内存简单剖析

减少不必要的容量

//使容量大小与元素个数对齐,减少空间浪费
public void trimToSize() {
       modCount++;
       if (size < elementData.length) {
           elementData = (size == 0)
             ? EMPTY_ELEMENTDATA
             : Arrays.copyOf(elementData, size);
       }
   }

查看数组下标

//首次出现元素o
 public int indexOf(Object o) {
     return indexOfRa
     nge(o, 0, size);
 }
 
 //从后往前数,首次出现元素o;原理与indexOf(Object o)一样
 public int lastIndexOf(Object o) {
     return lastIndexOfRange(o, 0, size);
 }
 
 int indexOfRange(Object o, int start, int end) {
     Object[] es = elementData;
     if (o == null) {
         for (int i = start; i < end; i++) {
             if (es[i] == null) {
                 return i;
             }
         }
     } else {
         for (int i = start; i < end; i++) {
             if (o.equals(es[i])) {
                 return i;
             }
         }
     }
     return -1;
 }
 

转换数组

转换为数组有两个方法,一个返回Object数组;一个返回指定类型数组。


 public Object[] toArray() {
     return Arrays.copyOf(elementData, size);
 }
 
 // 如果a的长度小于size 返回一个新的运行时动态数组,反之则在在a数组上拷贝于element元素
 public <T> T[] toArray(T[] a) {
     if (a.length < size)
         // Make a new array of a's runtime type, but my contents:
         return (T[]) Arrays.copyOf(elementData, size, a.getClass());
     System.arraycopy(elementData, 0, a, 0, size);
     if (a.length > size)
         a[size] = null;
     return a;
 }

eg:

List<String > list = new ArrayList<>();
       list.add("123");
       list.add("58");
       String  [] a = {"5","s","qq","12","ss","qq","ss"};
          list.toArray(a);
       for (String b :a) System.out.print(b+" ");
       System.out.println();
       a=new String[]{"1"};
       list.toArray(a);
       for (String b :a) System.out.print(b+" ");

结果:

修改指定下标元素

源代码里用checkIndex(int index, int length)检测小标是否越界异常

 public E set(int index, E element) {
       Objects.checkIndex(index, size);
       E oldValue = elementData(index);
       elementData[index] = element;
       return oldValue;
   }
   
   //java.lang.Obects
   public static
   int checkIndex(int index, int length) {
       return Preconditions.checkIndex(index, length, null);
   }
   
   //jdk.internal.util.Preconditions
   //BiFunction 是一个函数式接口
    public static <X extends RuntimeException>
   int checkIndex(int index, int length,
                  BiFunction<String, List<Integer>, X> oobef) {
       if (index < 0 || index >= length)
           throw outOfBoundsCheckIndex(oobef, index, length);
       return index;
   }
   

删除元素

//删除指定元素
public boolean remove(Object o) {
       final Object[] es = elementData;
       final int size = this.size;
       int i = 0;
       found: {
           if (o == null) {
               for (; i < size; i++)
                   if (es[i] == null)
                       break found;
           } else {
               for (; i < size; i++)
                   if (o.equals(es[i]))
                       break found;
           }
           return false;
       }
       fastRemove(es, i);
       return true;
   }
   
   //快速删除指定下标元素,私有方法,对象不能直接访问调用fastRemove
   private void fastRemove(Object[] es, int i) {
       modCount++;
       final int newSize;
       if ((newSize = size - 1) > i)
           System.arraycopy(es, i + 1, es, i, newSize - i);
       es[size = newSize] = null;
   }
   //删除数组每个元素,都赋值为null
    public void clear() {
       modCount++;
       final Object[] es = elementData;
       for (int to = size, i = size = 0; i < to; i++)
           es[i] = null;
   }

取与集合c交集的元素,batchRemove略微复杂,粗看第一遍没看懂。


public boolean retainAll(Collection<?> c) {
       return batchRemove(c, true, 0, size);
   }
//complement 为 true 则取与c交集元素,否则取差集
  boolean batchRemove(Collection<?> c, boolean complement,
                       final int from, final int end) {
       Objects.requireNonNull(c);
       final Object[] es = elementData;
       int r;
       // Optimize for initial run of survivors
       for (r = from;; r++) {
           if (r == end)
               return false;
           if (c.contains(es[r]) != complement)
               break;
       }
       int w = r++;
       try {
           for (Object e; r < end; r++)
               if (c.contains(e = es[r]) == complement)
                   es[w++] = e;
       } catch (Throwable ex) {
           // Preserve behavioral compatibility with AbstractCollection,
           // even if c.contains() throws.
           System.arraycopy(es, r, es, w, end - r);
           w += end - r;
           throw ex;
       } finally {
           modCount += end - w;
           shiftTailOverGap(es, w, end);
       }
       return true;
   }
   
// 移动数组,清除数组末尾 hi - lo 长度的元素
private void shiftTailOverGap(Object[] es, int lo, int hi) {
       System.arraycopy(es, hi, es, lo, size - hi);
       for (int to = size, i = (size -= hi - lo); i < to; i++)
           es[i] = null;
   }

内部类

ArrayList 私有内部类 Itr Itr作为顺序迭代器对象

arrayList 私有内部类 ListItr 实现了Itr,增强型的顺序遍历迭代器,支持从后向前遍历元素

ArrayList 内部类SubListSubList对象代表一个List的某个区间中的所有元素的集合

ArrayList 内部类 ArrayListSpliterator 在ArrayList中的spliterator方法中返回的正是ArrayListSpliterator对象,ArrayListSpliterator是ArrayList中实现的并发迭代器