前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入探讨源码--ArrayList

深入探讨源码--ArrayList

作者头像
田维常
发布2020-03-25 10:36:19
2230
发布2020-03-25 10:36:19
举报
文章被收录于专栏:Java后端技术栈cwnait

目录

深入探讨源码之ArrayList ArrayList 类图ArrayList的数据结构ArrayList的关键属性ArrayList构造方法ArrayList常用方法add方法ArrayList中的fast-fail机制add(i,o)方法set(i,o)方法get(i)方法remove(index)方法remove(Object)方法clear方法indexOf(o)方法

深入探讨源码之ArrayList

java.util.ArrayList位于JDK的rt.jar中;出生于JDK1.2。本文JDK版本基于JDK1.8

List接口的可调整大小的数组实现,实现了所有可选的List操作,允许所有数据都为null。类Vector类似,ArrayList是线程不安全的,Vector是线程安全的。

ArrayList类图

上面这张图基本上描述的很清晰了,实现了四个接口一个抽象类。它继承了AbstractList抽象类,实现了List、RandomAccess, Cloneable,Serializable接口。

  • 它继承于AbstractList,实现了ListRandomAccess随机访问,Cloneable可克隆, java.io.Serializable序列化]这些接口。
  • ArrayList继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
  • ArrayList实现了RandmoAccess接口,即提供了随机访问功能。
  • ArrayList实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
  • ArrayList实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
  • 和Vector不同,ArrayList中的操作不是线程安全的。所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList
ArrayList的数据结构

数据结构往往是它的灵魂所在,理解底层的数据结构其实就理解了该类的实现思路,具体的实现细节再具体分析。ArrayList的数据结构是:

说明:底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。我们对ArrayList类的实例的所有的操作底层都是基于数组的。

ArrayList的关键属性
代码语言:javascript
复制
//初始化大小为10
private static final int DEFAULT_CAPACITY = 10;
//空对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//缺省空对象数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存数据的数组
transient Object[] elementData;
//当前ArrayList的大小
private int size;
//ArrayList最大容量
//Integer.MAX_VALU=(2的31次方)-1
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
ArrayList构造方法
代码语言:javascript
复制
//开发人员使用最多的构造方法,全部使用默认参数
public ArrayList() {
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//部分开发人会用的构造方法,可以指定初始大小的
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(Collection<? extends E> c) {
     elementData = c.toArray();
     if ((size = elementData.length) != 0) {
       // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
          elementData = Arrays.copyOf(elementData, size, Object[].class);
      } else {
           // replace with empty array.
           this.elementData = EMPTY_ELEMENTDATA;
     }
 }
ArrayList常用方法
add方法

ArrayList中添加元素,使用demo

代码语言:javascript
复制
public class ArrayListDemo {
    public static void main(String[] args) {
        ArrayList<String> arrayList=new ArrayList<>();
        arrayList.add("Java后端技术栈");
        arrayList.add("tian");
        arrayList.add("Java");
        arrayList.add("DB");
        Iterator<String> mIterator = arrayList.iterator();
        while (mIterator.hasNext()){
            if ("tian".equals(mIterator.next())){
                arrayList.add("C");
            }
        }
    }
}

add方法源码分析

代码语言:javascript
复制
public boolean add(E e) {
      //确保内部容量,size为elementData的长度,本次添加一个
      //即size+1
      //使用第一个构造方法,第一次添加数据的时候,size=0
      ensureCapacityInternal(size + 1);  // Increments modCount!!
      elementData[size++] = e;
      return true;
}
private void ensureCapacityInternal(int minCapacity) {
      //elementData是空数组,minCapacity=0+1=1
      ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
      //两个数组都是空的,所以这里是相等的
      if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
         //返回DEFAULT_CAPACITY=10和minCapacity=1比较,谁大返回谁
          //那么这里就返回10
         return Math.max(DEFAULT_CAPACITY, minCapacity);
      }
      return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
      //用来标记当前arraylist集合操作变化的次数
      modCount++;
      //如果minCapacity>elementData.length
      //minCapacity=10,elementData.length=0
      if (minCapacity - elementData.length > 0){
            grow(minCapacity);
      }
 }
private void grow(int minCapacity) {
    //将扩充前的elementData大小给oldCapacity
    int oldCapacity = elementData.length;
    //newCapacity就是1.5倍的oldCapacity
    //第一次添加元素的时候,newCapacity依旧是0
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //这句话就是适应于elementData就空数组的时候,length=0,
    //那么oldCapacity=0,newCapacity=0,所以这个判断成立,
    //在这里就是真正的初始化elementData的大小了,
    //就是为10.前面的工作都是准备工作。
    if (newCapacity - minCapacity < 0){
            newCapacity = minCapacity;
    }
    //如果newCapacity超过了最大的容量限制,就调用hugeCapacity,
    //也就是将能给的最大值给newCapacity
    //比较一下newCapacity与(2的31次方)-1谁大
    if (newCapacity - MAX_ARRAY_SIZE > 0){
            newCapacity = hugeCapacity(minCapacity);
    }
    // minCapacity is usually close to size, so this is a win:
    //新的容量大小已经确定好了,就copy数组,改变容量大小。
    elementData = Arrays.copyOf(elementData, newCapacity);
}
//OOM或者就是赋值最大容量
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
          throw new OutOfMemoryError();
    //如果minCapacity都大于MAX_ARRAY_SIZE,那么就Integer.MAX_VALUE返回,
    //反之将MAX_ARRAY_SIZE返回。因为maxCapacity是三倍的minCapacity,
    //可能扩充的太大了,就用minCapacity来判断了。
    //Integer.MAX_VALUE:2147483647   MAX_ARRAY_SIZE:2147483639
    //也就是说最大也就能给到第一个数值。还是超过了这个限制,
    //就要溢出了。相当于arraylist给了两层防护。
    return (minCapacity > MAX_ARRAY_SIZE) ?
       Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
ArrayList中的fast-fail机制

运行上面demo

还记得上面源码中ensureExplicitCapacity方法中有个modCount变量么?

抛出的异常正是我们刚才提到的ConcurrentModificationException 并发修改异常,正是由于fail-fast机制导致的。在什么情况下会出现该异常呢?我先简单说下,一般情况下有两种情况,一种情况发生在多线程操作,当a线程正在通过迭代器操作集合arrayList时,同时b线程对arrayList进行添加或者删除元素,会触发fail-fast机制,抛出该异常。另一种情况是在迭代集合arrayList的过程中对arrayList集合进行元素添加或者删除操作时,会触发fail-fast机制,抛出该异常。归根结底就是当我们进行集合数据迭代时,有其他操作修改了当前ArraylistmodCount的值,导致modCount != expectedModCount,会抛出该异常。

代码语言:javascript
复制
public Iterator<E> iterator() {
     return new Itr();
}
private class Itr implements Iterator<E> {
     // next 元素角标
     int cursor;       // index of next element to return
     // last 元素角标
     int lastRet = -1; // index of last element returned; -1 if no such
     int expectedModCount = modCount;
    
     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];
     }
    final void checkForComodification() {
            //当modCount != expectedModCount时,
            //会抛出ConcurrentModificationException异常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
    }
}

add、remove、clear元素都会使得modCount变化。

add(i,o)方法

添加到指定的位置。

使用demo

代码语言:javascript
复制
public class ArrayListDemo1 {
    public static void main(String[] args) {
        ArrayList<String> arrayList=new ArrayList<>();
        arrayList.add(1,"Java后端技术栈");
    }
}

具体源码

代码语言:javascript
复制
//index为即将要存放数据的位置,element为数据
public void add(int index, E element) {
        //添加之前,先检查index是否满足条件
        rangeCheckForAdd(index);
        //前面已经分析了
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将index及其后边的所有的元素整块后移,空出index位置
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //将元素存放在index位置上
        elementData[index] = element;
        //数组大小加1
        size++;
}
private void rangeCheckForAdd(int index) {
        //如果使用第一种构造方法,那么size==0
        //如果index>size或者index<0就会抛数组越界异常
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

运行demo,这个方法慎用,平时工作使用的不是很多,因为在使用的时候,需要保证不满足

index > size和Index<0中的任何一个条件,否则将出现:

set(i,o)方法

使用demo

代码语言:javascript
复制
public class ArrayListDemo1 {
    public static void main(String[] args) {
        ArrayList<String> arrayList=new ArrayList<>();
        arrayList.add("Java后端技术栈");
        arrayList.add("tian");
        arrayList.add("Java");
        arrayList.set(1,"C");
        for (String string:arrayList){
            System.out.println(string);
        }
    }
}

源码

代码语言:javascript
复制
//把Index位置上的旧值拿出来,然后把新值放进去
public E set(int index, E element) {
        rangeCheck(index);
        //获取index位置上的旧值
        E oldValue = elementData(index);
        //把index位置上设置新值
        elementData[index] = element;
        //返回旧值
        return oldValue;
}
//index大于ArrayList的大小,就明显是数组越界了。
private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
get(i)方法

获取数据

代码语言:javascript
复制
//检查index是否越界,然后获取index上的数据
public E get(int index) {
        rangeCheck(index);
        return elementData(index);
 }
 E elementData(int index) {
        return (E) elementData[index];
 }
remove(index)方法
代码语言:javascript
复制
    public E remove(int index) {
        //范围检查
        rangeCheck(index);
        //modCount加1
        modCount++;
        //取出旧值
        E oldValue = elementData(index);
        //获取从哪个下标开始移动数据
        int numMoved = size - index - 1;
        //如果删除的不是最后一个元素
        if (numMoved > 0)
            //删除的元素到最后的元素整块前移
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将最后一个元素设为null,方便垃圾回收集在下次gc的时候就会回收掉了
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }
remove(Object)方法

和remove(index)雷士,只是多了一层遍历而已。性能明细没有remove(index)好。

代码语言:javascript
复制
    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;
    }
   //和前面remove(index) 类似了
    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; // clear to let GC do its work
    }
clear方法
代码语言:javascript
复制
public void clear() {
        modCount++;
        // clear to let GC do its work
        //清楚就是把ArrayList中的所有对象置为null,方便垃圾收集器收集
        for (int i = 0; i < size; i++)
            elementData[i] = null;
         //最后把size置为0
         size = 0;
 }
indexOf(o)方法

获取o所在的下标,如果ArrayList中没有o,那么就返回 -1;

代码语言:javascript
复制
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-03-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java后端技术栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 深入探讨源码之ArrayList
    • ArrayList类图
      • ArrayList的数据结构
        • ArrayList的关键属性
          • ArrayList构造方法
            • ArrayList常用方法
              • add方法
              • ArrayList中的fast-fail机制
              • add(i,o)方法
              • set(i,o)方法
              • get(i)方法
              • remove(index)方法
              • remove(Object)方法
              • clear方法
              • indexOf(o)方法
          相关产品与服务
          文件存储
          文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档