ArrayList源码分析(fail-fast机制和扩容)

来源:互联网 发布:淘宝综合排名怎么算 编辑:程序博客网 时间:2024/05/16 17:46
针对ArrayList,需要明白以下几点:
  • 线程不安全~
  • 扩容机制:扩容为1.5倍+1,写法为oldCapacity+(oldCapacity>>1),防止整数溢出~
  • 构造方法:在jdk1.8以后,初始化时不分配大小,只有在第一次添加数据时才创建大小为10的数组~
  • fast-fail机制:增删改时都有modCount变量记录修改次数,和expectModCount比较,以便迭代列表修改数据时能够报ConcurrentModificationException~
  • MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8~
  • 数组复制时使用System.arraycopy()~
针对上面的几点,我们展开说明下:

Arrays.copyOf()和System.arraycopy()

区别直接看二者源码:
public static native void arraycopy(Object src,   //源数组                                    int  srcPos,  //源数组中的起始位置                                    Object dest,  //目标数组                                    int destPos,  //目标数组中的起始位置                                    int length    //要复制的数组元素的数量);  
//非基本类型  public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {          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;   }  //基本数据类型  public static int[] copyOf(int[] original, int newLength) {          int[] copy = new int[newLength];          System.arraycopy(original, 0, copy, 0,                           Math.min(original.length, newLength));          return copy;  } //original - 要复制的数组 //newLength - 要返回的副本的长度 //newType - 要返回的副本的类
区别:
  • arraycopy()需要目标数组,将原数组copy到你自己定义的数组里,而且可以选择copy的起点长度以及放入新数组中的位置;
  • copyOf()是系统自动在内部新建一个数组,调用arraycopy()进行复制。
总结:
Array.copyOf()可以看作是受限的System.arraycopy(),它主要是用来将原数组全部拷贝到一个新长度的数组,适用于数组扩容

MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8

      一个数组对象,例如int类型,和一个标准的java对象很类似。主要的区别在于:数组对象有一个用来存储数据大小的额外元数据片。一个数组对象的metadata包括:
①Class:指向类信息的指针用来表名对象类型。如整形数组,这个指针就指向int[]类;
②Flags:一个表示对象状态的flags集合,包括对象的hashcode(如果有的话)和对象的判断(表示一个对象是否是数组);
③Lock:对象的同步信息,表明对象当前是否被synchronized;
④Size:数组的大小。
整型的最大值为2^31 = 2,147,483,648,因为数组本身需要8 bytes来存储大小2,147,483,648,因此数组的最大值为2^31-8。

扩容 机制

先看下jdk1.6的扩容机制:
public void ensureCapacity(int minCapacity) {      modCount++;      int oldCapacity = elementData.length;      if (minCapacity > oldCapacity) {          Object oldData[] = elementData;          int newCapacity = (oldCapacity * 3)/2 + 1;  //没有考虑整形溢出问题        if (newCapacity < minCapacity)              newCapacity = minCapacity;          // minCapacity is usually close to size, so this is a win:          elementData = Arrays.copyOf(elementData, newCapacity);      }  }  
int newCapacity = (oldCapacity * 3)/2 + 1;和oldCapacity+(oldCapacity>>1);效果是一样的,都相当于1.5倍,但实际上有很大区别:①前者的乘除运算数学结果比后者大1,如oldCapaticy=10时,前者是16,后者是15;②在oldCapacity比较大时运算结果不一样,如oldCapacity=Integer.MAX_VALUE即10^9,前者算出-647483647,后者算出1500000000.
因此1.7及以上版本jdk对这两个问题做了修改:
// 手动扩容方法(可以外部调用,不过大多数情况都是List<?> = new ArrayList<>(),这样是调用不到这个方法的)  // 这个方法只是简单区别下list是不是通过 new ArrayList() 来创建的,这一点前面说了  // 如果是,则尝试最小扩容10个,不是则尝试扩容指定个,具体也是通过内部扩容方法完成容量确保  public void ensureCapacity(int minCapacity) {      int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)          // any size if not default element table          ? 0          // larger than default for default empty table. It's already          // supposed to be at default size.          : DEFAULT_CAPACITY;        if (minCapacity > minExpand) {          ensureExplicitCapacity(minCapacity);      }  }    // 下面是内部扩容相关的几个方法的代码  private void ensureCapacityInternal(int minCapacity) {      if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {          minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);      }        ensureExplicitCapacity(minCapacity);  }    private void ensureExplicitCapacity(int minCapacity) {      modCount++;        // overflow-conscious code 考虑int型溢出      if (minCapacity - elementData.length > 0)          grow(minCapacity);  }    private void grow(int minCapacity) {      // overflow-conscious code 考虑int型溢出      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);  }    private static int hugeCapacity(int minCapacity) {      if (minCapacity < 0) // overflow int型溢出,直接报错          throw new OutOfMemoryError();      return (minCapacity > MAX_ARRAY_SIZE) ?          Integer.MAX_VALUE :          MAX_ARRAY_SIZE;  }  

构造方法

//jdk 1.6public ArrayList(){    this(10);}//jdk1.7private static final Object[] EMPTY_ELEMENTDATA = {}; public ArrayList() {      super();      this.elementData = EMPTY_ELEMENTDATA;  }//jdk 1.8private static final Object[] EMPTY_ELEMENTDATA = {};  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};  public ArrayList() {      this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;  } 
对比可知,随着版本的增加,无参构造方法的底层数组elementData大小默认为0,而不是之前的10。也就是,懒初始化,只有在第一次添加数据时,才开始真正的初始化工作。
带参数的构造方法,基本一致:
// 1.6  public ArrayList(int initialCapacity) {      super();      if (initialCapacity < 0)          throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);      this.elementData = new Object[initialCapacity];  }    // 1.7 跟1.6的一样  public ArrayList(int initialCapacity) {      super();      if (initialCapacity < 0)          throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);      this.elementData = new Object[initialCapacity];  }    // 1.8  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);      }  }  

fail-fast机制

"快速失败",是java集合的一种错误检测机制,某个线程对collection进行迭代时,不允许其他线程对该collection进行结构上的修改。java.util包中的所有集合类都是快速失败的,而java.util.concurrent包中的集合类都是安全失败的;快速失败的迭代器在出错时会抛出ConcurrentModificationException,而安全失败的迭代器从不抛出这个异常。
总结:            fast-fail事件产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过iterator去遍历集合时,该集合的内容被其他线程所改变;则会抛出ConcurrentModificationException异常。
        fast-fail解决办法:通过util.concurrent集合包下的相应类去处理,则不会产生fast-fail事件。如CopyOnWriteArrayList
那么,ConcurrentModificationException是如何产生的呢?
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { ... // AbstractList中唯一的属性 // 用来记录List修改的次数:每修改一次(添加/删除等操作),将modCount+1 protected transient int modCount = 0;  // 返回List对应迭代器。实际上,是返回Itr对象。 public Iterator<E> iterator() {  return new Itr(); }  // Itr是Iterator(迭代器)的实现类 private class Itr implements Iterator<E> {  int cursor = 0;   int lastRet = -1;   // 修改数的记录值。  // 每次新建Itr()对象时,都会保存新建该对象时对应的modCount;  // 以后每次遍历List中的元素的时候,都会比较expectedModCount和modCount是否相等;  // 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。  int expectedModCount = modCount;   public boolean hasNext() {   return cursor != size();  }   public E next() {   // 获取下一个元素之前,都会判断“新建Itr对象时保存的modCount”和“当前的modCount”是否相等;   // 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。   checkForComodification();   try {    E next = get(cursor);    lastRet = cursor++;    return next;   } catch (IndexOutOfBoundsException e) {    checkForComodification();    throw new NoSuchElementException();   }  }   public void remove() {   if (lastRet == -1)    throw new IllegalStateException();   checkForComodification();    try {    AbstractList.this.remove(lastRet);    if (lastRet < cursor)     cursor--;    lastRet = -1;    expectedModCount = modCount;   } catch (IndexOutOfBoundsException e) {    throw new ConcurrentModificationException();   }  }   final void checkForComodification() {   if (modCount != expectedModCount)    throw new ConcurrentModificationException();  } } ...}
在执行next()和remove()方法时,都会执行checkForComodication()方法,目的就是为了检测modCount和expectedModCount是否一致。在Itr类中,初始化对象时expectedModCount被赋值为modCount。而通过查看ArrayList源码可知,在add(),remove(),clear()等方法中(涉及到修改集合中元素个数),都会改变modCount值。
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 商业住房贷款利率 陕西省住房公积金管理中心 合肥住房公积金管理中心 长春市住房公积金 山东省住房城乡建设厅 2019各银行住房商贷利率一览表 住房公积金查询入口 陕西省住房和城乡建设厅网 沧州住房公积金个人查询入口 包头市住房公积金管理中心 佛山住房公积金中心 个人住房商业性贷款 住房公积金装修贷款能贷多少 邵阳住房公积金管理中心 工资4000住房公积金一般交多少 西安住房保障管理局网站 佛山住房公积金 住房城乡建设部 河南省住房和城乡建设厅网 北京住房公积金 广州住房公积金管理中心 西安住房公积金 成都住房公积金管理中心 宜春住房公积金 安徽省住房和城乡建设厅 四川城乡住房建设厅 陕西省住房公积金中心 深圳市住房和建设局 南宁市住房保障和房产管理局 住房公积金是什么 十堰住房公积金查询 广安住房公积金查询 枣庄住房公积金管理中心 南宁市住房公积金查询 陕西住房公积金查询网 南充市住房公积金管理中心 南宁住房公积金 晋中住房公积金查询个人账户 保定住房公积金查询 淮南市住房公积金查询 苏州市住房公积金管理中心