前言 ArrayList继承了AbstractList,实现了List。ArrayList在工作中经常用到,所以要弄懂这个类是极其重要的。
构造图如下:
ArrayList简介 定义 1 2 public  class  ArrayList <E > extends  AbstractList <E >        implements  List <E >, RandomAccess , Cloneable , java .io .Serializable  
ArrayList 是一个数组队列 ,相当于动态数组 。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandomAccess接口,即提供了随机访问功能。RandomAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。
ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
和Vector不同,ArrayList中的操作不是线程安全的 !所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
ArrayList属性 顾名思义,ArrayList就是用数组实现的List容器,既然是用数组实现,当然底层用数组来保存数据。
1 2 3 4 transient  Object[] elementData;private  int  size;
ArrayList包含了两个重要的对象:elementData 和 size。
elementData 是”Object[]类型的数组”,它保存了添加到ArrayList中的元素。实际上,elementData是个动态数组,我们能通过构造函数ArrayList(int initialCapacity)来执行它的初始容量为initialCapacity;如果通过不含参数的构造函数ArrayList()来创建ArrayList,则elementData的容量默认是10 。elementData数组的大小会根据ArrayList容量的增长而动态的增长,具体的增长方式,请参考源码分析中的ensureCapacity()函数; 
size 则是动态数组的实际大小。 
 
ArrayList构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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);     } } public  ArrayList ()           this .elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public  ArrayList (Collection<? extends E> c)      elementData = c.toArray();     if  ((size = elementData.length) != 0 ) {         if  (elementData.getClass() != Object[].class )              elementData   = Arrays.copyOf(elementData, size, Object[].class ) ;    } else  {         this .elementData = EMPTY_ELEMENTDATA;     } } 
第一个构造方法使用提供的initialCapacity来初始化elementData数组的大小。 
第二个构造方法调用第一个构造方法并传入参数10,即默认elementData数组的大小为10。 
第三个构造方法则将提供的集合转成数组返回给elementData(返回若不是Object[]将调用Arrays.copyOf方法将其转为Object[])。 
 
源码解析 增加 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 public  boolean  add (E e)         ensureCapacityInternal(size + 1 );            elementData[size++] = e;     return  true ; } public  void  add (int  index, E element)         rangeCheckForAdd(index);        ensureCapacityInternal(size + 1 );            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);            System.arraycopy(a, 0 , elementData, size, numNew);          size += numNew;     return  numNew != 0 ; } public  boolean  addAll (int  index, Collection<? extends E> c)      rangeCheckForAdd(index);     Object[] a = c.toArray();     int  numNew = a.length;     ensureCapacityInternal(size + numNew);          int  numMoved = size - index;          if  (numMoved > 0 )         System.arraycopy(elementData, index, elementData, index + numNew, numMoved);        System.arraycopy(a, 0 , elementData, index, numNew);     size += numNew;     return  numNew != 0 ; } private  void  ensureCapacityInternal (int  minCapacity)      if  (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {         minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);     }     ensureExplicitCapacity(minCapacity); } private  void  ensureExplicitCapacity (int  minCapacity)      modCount++;     if  (minCapacity - elementData.length > 0 )         grow(minCapacity); } private  void  grow (int  minCapacity)           int  oldCapacity = elementData.length;          int  newCapacity = oldCapacity + (oldCapacity >> 1 );          if  (newCapacity - minCapacity < 0 )         newCapacity = minCapacity;     if  (newCapacity - MAX_ARRAY_SIZE > 0 )         newCapacity = hugeCapacity(minCapacity);          elementData = Arrays.copyOf(elementData, newCapacity); } 
删除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84   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 ;           return  oldValue;   }         public  boolean  remove (Object o)                if  (o == null ) {           for  (int  index = 0 ; index < size; index++)                            if  (elementData[index] == null ) {                   fastRemove(index);                   return  true ;               }       } else  {           for  (int  index = 0 ; index < size; index++)               if  (o.equals(elementData[index])) {                   fastRemove(index);                   return  true ;               }       }       return  false ;   }      public  boolean  removeAll (Collection<?> c)         Objects.requireNonNull(c);       return  batchRemove(c, false );   }      private  boolean  batchRemove (Collection<?> c, boolean  complement)         final  Object[] elementData = this .elementData;       int  r = 0 , w = 0 ;       boolean  modified = false ;       try  {           for  (; r < size; r++)               if  (c.contains(elementData[r]) == complement)                   elementData[w++] = elementData[r];       } finally  {           if  (r != size) {               System.arraycopy(elementData, r,                                elementData, w,                                size - r);               w += size - r;           }           if  (w != size) {                              for  (int  i = w; i < size; i++)                   elementData[i] = null ;               modCount += size - w;               size = w;               modified = true ;           }       }       return  modified;   }      private  void  fastRemove (int  index)         modCount++;              int  numMoved = size - index - 1 ;       if  (numMoved > 0 )           System.arraycopy(elementData, index+1 , elementData, index, numMoved);       elementData[--size] = null ; } 
代码是很简单,主要需要特别关心的就两个地方:
数组扩容 
数组复制 
 
这两个操作都是极费效率的,最惨的情况下(添加到list第一个位置,删除list最后一个元素或删除list第一个索引位置的元素)时间复杂度可达O(n)。
上面讲增加元素可能会进行扩容,而删除元素却不会进行缩容,如果在已删除为主的场景下使用list,一直不停的删除而很少进行增加,那么会出现什么情况?再或者数组进行一次大扩容后,我们后续只使用了几个空间,如何处理这种情况呢?
1 2 3 4 5 6 7 8 9 10 11 12 public  void  trimToSize ()      modCount++;          if  (size < elementData.length) {         elementData = (size == 0 )           ? EMPTY_ELEMENTDATA           : Arrays.copyOf(elementData, size);     } } 
size和length是不同的:size是私有变量,只能通过size()来访问,返回当前数组的实际元素大小;length是数组的属性,返回当前数组的容量。
关于ArrayList删除元素的坑 关于ArrayList的remove方法,在使用中经常会遇到坑,这也是面试中经常会问的点 —— 输出是什么?为什么会这样?我觉得有必要把它单独拿出来说明一下。
我们经常遇到的需求是:当集合中的某些元素符合一定的条件时,需要删除这个元素,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public  class  ListTest      public  static  void  main (String[] args)          List<Integer> intList = new  ArrayList<Integer>();          Collections.addAll(intList, 1 , 2 , 3 , 5 , 6 );                  for (int  i = 0 , size = intList.size(); i < size; i++) {              Integer value = intList.get(i);                          if (value == 3  || value == 5 ) {                 intList.remove(i);              }          }          System.out.println(intList);       }   } 
在执行之后,会抛出IndexOutOfBoundsException,因为ArrayList删除掉符合条件的元素后,长度动态发生了改变,由于长度只获取了一次,导致越界。
去掉for循环优化的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  class  ListTest      public  static  void  main (String[] args)          List<Integer> intList = new  ArrayList<Integer>();          Collections.addAll(intList, 1 , 2 , 3 , 5 , 6 );          for (int  i = 0 ; i < intList.size(); i++) {              Integer value = intList.get(i);                          if (value == 3  || value == 5 ) {                 intList.remove(i);              }          }          System.out.println(intList);       }   } 
输出:[1, 2, 5, 6]。
因为在i == 2时,值为3,删除后,后面的元素往前补一位,所以跳过了5。
使用foreach:
1 2 3 4 5 6 7 8 9 10 11 12 13 public  class  ListTest      public  static  void  main (String[] args)          List<Integer> intList = new  ArrayList<Integer>();          Collections.addAll(intList, 1 , 2 , 3 , 5 , 6 );          for (Integer value : intList) {                          if (value == 3  || value == 5 ) {                 intList.remove(value);              }          }          System.out.println(intList);       }   } 
执行后,会抛出ConcurrentModificationException,意思是并发修改异常,异常追踪信息如下:
1 2 3 4 Exception inthread "main" java.util.ConcurrentModificationException      atjava.util.AbstractList$Itr.checkForComodification(AbstractList.java:449)      at java.util.AbstractList$Itr.next(AbstractList.java:420)      at ListTest.main(ListTest.java:13) 
大概看出是执行到AbstractList中内部类的checkForComodification方法抛出的异常。集合遍历是使用Iterator,Iterator是工作在一个独立的线程中,并且拥有一个互斥锁。Iterator被创建之后会建立一个指向原来对象的单链索引表 ,当原来的对象数量发生变化时,这个索引表的内容不会同步改变 ,所以当索引指针往后移动的时候就找不到要迭代的对象。
AbstractList$Itr.checkForComodification:
1 2 3 4 final  void  checkForComodification ()      if  (modCount != expectedModCount)         throw  new  ConcurrentModificationException(); } 
所以记住一点:Iterator在工作的时候是不允许被迭代的对象被改变的 。
那么正确删除ArrayList元素的姿势是什么呢?这里有三种方法:
使用Iterator的remove方法 该方法会删除当前迭代对象的同时,维护索引的一致性。
Iterator的remove方法源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public  void  remove ()      if  (lastRet < 0 )         throw  new  IllegalStateException();     checkForComodification();     try  {         AbstractList.this .remove(lastRet);         if  (lastRet < cursor)             cursor--;         lastRet = -1 ;         expectedModCount = modCount;     } catch  (IndexOutOfBoundsException e) {         throw  new  ConcurrentModificationException();     } } 
使用Iterator删除元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  class  ListTest      public  static  void  main (String[] args)          List<Integer> intList = new  ArrayList<Integer>();          Collections.addAll(intList, 1 , 2 , 3 , 5 , 6 );          Iterator<Integer> it = intList.iterator();          while (it.hasNext()) {              Integer value = it.next();              if (value == 3  || value == 5 ) {                 it.remove();              }          }          System.out.println(intList);       }   } 
输出:[1, 2, 6]。
自己维护索引 即,删除元素后,索引-1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  class  ListTest      public  static  void  main (String[] args)          List<Integer> intList = new  ArrayList<Integer>();          Collections.addAll(intList, 1 , 2 , 3 , 5 , 6 );          for (int  i = 0 ; i < intList.size(); i++) {              Integer value = intList.get(i);              if (value == 3  || value == 5 ) {                 intList.remove(i);                 i--;              }          }           System.out.println(intList);       }   } 
输出:[1, 2, 6]。
从后向前遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 public  class  ListTest      public  static  void  main (String[] args)          List<Integer> intList = new  ArrayList<Integer>();          Collections.addAll(intList, 1 , 2 , 3 , 5 , 6 );          for (int  i = intList.size() - 1 ; i >= 0 ; i--) {              Integer value = intList.get(i);              if (value == 3  || value == 5 ) {                 intList.remove(i);              }          }           System.out.println(intList);       }   } 
输出:[1, 2, 6]。
更新 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  E set (int  index, E element)           rangeCheck(index);          E oldValue = elementData(index);          elementData[index] = element;          return  oldValue; } 
查找 1 2 3 4 5 public  E get (int  index)      rangeCheck(index);     return  (E) elementData[index]; } 
总结 
ArrayList 实际上是通过一个数组去保存数据的。当我们构造ArrayList时;若使用默认构造函数,则ArrayList的默认容量大小是10。 
当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量=“原始容量 * 1.5”。 
ArrayList的克隆函数,即是将全部元素克隆到一个数组中。 
ArrayList实现java.io.Serializable的方式。当写入到输出流时,先写入“容量”,再依次写入“每一个元素”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。 
 
ArrayList遍历方式 ArrayList支持3种遍历方式:
迭代器遍历 1 2 3 4 5 Integer value = null ; Iterator iter = list.iterator(); while  (iter.hasNext()) {    value = (Integer)iter.next(); } 
随机访问 即通过索引值去遍历,由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。
1 2 3 4 5 Integer value = null ; int  size = list.size();for  (int  i = 0 ; i < size; i++) {    value = (Integer)list.get(i);         } 
for-each遍历 1 2 3 4 Integer value = null ; for  (Integer i : list) {    value = i; } 
遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低。 
总结 ArrayList和LinkedList的区别 
ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 
对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。 
 
ArrayList和Vector的区别 
Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。 
Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。 
Vector还有一个子类Stack. 
 
附:集合框架思维导图