专栏首页Java后端技术栈cwnait深入探讨源码--ArrayList

深入探讨源码--ArrayList

目录

深入探讨源码之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的关键属性

//初始化大小为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构造方法

//开发人员使用最多的构造方法,全部使用默认参数
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

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方法源码分析

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,会抛出该异常。

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

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

具体源码

//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

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);
        }
    }
}

源码

//把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)方法

获取数据

//检查index是否越界,然后获取index上的数据
public E get(int index) {
        rangeCheck(index);
        return elementData(index);
 }
 E elementData(int index) {
        return (E) elementData[index];
 }
remove(index)方法
    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)好。

    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方法
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;

    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;
    }

本文分享自微信公众号 - Java后端技术栈(t-j20120622),作者:田老师

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 精解四大集合框架:List核心知识总结

    Java集合框架早也是个老话题了,今天主要是总结一下关于Java中的集合框架List的核心知识点。肯定有人会问,这篇写的是List那接下来就还有三篇?是的,ja...

    田维常
  • 面试官:什么是面向对象?

    Java 是一个支持并发、基于类和面向对象的计算机编程语言。面向对象软件开发具有以下优点:

    田维常
  • 6 年 Java 老兵 BAT 面试心经

    “ 一定要有一门自己的特长,不管是技术还好还是其他能力 ” 。我觉得这句话真的非常有道理,大家可以仔细思考一下。在这里再强调一点:公司不需要你什么都会,但是在某...

    田维常
  • Java集合-ArrayList源码解析-JDK1.8

    ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, Rando...

    Java学习录
  • Java之ArrayList解剖学

    回归基础,回归原理,你会有更深的领悟。今天来聊一聊在Java当中常用的一个集合类:ArrayList。

    23号杂货铺
  • Java集合源码分析之ArrayList

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

    须臾之余
  • 从源码上分析 ArrayList

    一份执着✘
  • ArrayList源码阅读笔记

    关于数组拷贝,谷歌了一下,说法不一,有说是深拷贝的,有说是浅拷贝的。暂时先放下,未来有机会再研究。

    三分恶
  • Java集合源码剖析——ArrayList源码剖析

    ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。

    Java团长
  • Java集合深度解析之ArrayList

    KS Knowledge Sharing 知识分享 现在是资源共享的时代,同样也是知识分享的时代,如果你觉得本文能学到知识,请把知识与别人分享。 转自:...

    互扯程序

扫码关注云+社区

领取腾讯云代金券