首页 > 编程语言 >深入探讨源码--ArrayList

深入探讨源码--ArrayList

时间:2023-04-28 19:34:50浏览次数:42  
标签:index minCapacity -- ArrayList elementData int 源码 size

持续推送技术干货

深入探讨源码--ArrayList_ci

目录

深入探讨源码之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类图

深入探讨源码--ArrayList_ci_02

上面这张图基本上描述的很清晰了,实现了四个接口一个抽象类。它继承了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的数据结构是:

深入探讨源码--ArrayList_List_03

说明:底层的数据结构就是数组,数组元素类型为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

深入探讨源码--ArrayList_数组_04

还记得上面源码中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中的任何一个条件,否则将出现:

深入探讨源码--ArrayList_ci_05

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

深入探讨源码--ArrayList_ci_06

标签:index,minCapacity,--,ArrayList,elementData,int,源码,size
From: https://blog.51cto.com/u_11702014/6235392

相关文章

  • Problem J: 括号匹配问题
    ProblemDescription在某个字符串(长度不超过100)中有左括号、右括号和大小写字母;规定(与常见的算数式子一样)任何一个左括号都从内到外与在它右边且距离最近的右括号匹配。写一个程序,找到无法匹配的左括号和右括号,输出原来字符串,并在下一行标出不能匹配的括号。不能匹配的左括号用......
  • Vue实战案例
    Vue项目案例结合之前学习的vue.js、脚手架、vuex、vue-router、axios、elementui等知识点,来开发前端项目案例(仅前端不含后端)。1.项目搭建其实就是将我们项目需要用到的组件都安装并配置好,需要做的事有:创建项目&运行项目vuecreate项目名称npmrunserveWebStorm集......
  • 正余弦函数
    #define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>#include<math.h>#include<Windows.h>     //=清屏#definepi3.1415926      //定义圆周率intmain(){ inti,j; doublea,b; while(1) { printf("正弦函数请输入‘1’,余弦函数请输入‘2......
  • JVM系列——java文件到JVM中的整个过程
    关注“Java后端技术栈“回复“面试”获取最新资料今天来聊聊从java文件到class文件,最后class文件是怎么到JVM中的。首先是编写一个HelloWorld.java类,然后通过这一系列的编译操作,最终成了HelloWorld.class文件。然后把HelloWorld.class文件加载到JVM中的整个过程:1,装载。查找和导入cl......
  • other初级语法
    1.other(限、adj)+n=others2.some....others 3.othertime no/any/everyother4.泛指其他人/事/物somestudents...;otherstudents...otherstudents=others.....5.特指剩余的全部20ofthestudents...   20 | 40  theothers6.one...theothe......
  • JVM系列——运行时数据区
    关注“Java后端技术栈”回复“面试”获取最新资料官网地址:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html摘要:对运行时数据区的介绍是:TheJavaVirtualMachinedefinesvariousrun-timedataareasthatareusedduringexecutionofaprogram.Someofthes......
  • Codeforces Round 868 (Div. 2) A-E题解
    比赛地址这把真不在状态,麻了,看来还得再练A.A-characteristic题意:给出n和k,要求构造只含1和-1数组a,存在k对(i,j)(i≠j),有a[i]*a[j]=1Solution令构造的数组有x个1和y个-1,那么其对于答案的贡献有\[x*(x-1)/2+y*(y-1)/2\]又因为有x+y=n,所以可以转化成关于x的一元二次方程化简后......
  • 面试被问ReentrantLock的公平锁与非公平锁
    关注Java后端技术栈“回复“面试”获取最新资料面试被问ReentrantLock的公平锁与非公平锁的区别以及实现。建议先阅读Java中的锁原理、锁优化、CAS、AQS,看这篇就对了!案例publicclassLockDemo{publicstaticvoidmain(String[]args){Locklock=newReentrant......
  • python日常工作处理-文件按比例分割数据
    python日常工作处理-文件按比例分割数据把一个保存用户id文本进行比例分割,比例为50%,分别另存为另外两个文件代码importrandominput_file='/Users/Desktop/2023-03-28.txt'group1_file='/Users/Desktop/group1_2023-03-28.txt'group2_file='/Users/Desktop/group2_......
  • PHP计算两个经纬度之间的据离
    直接上代码/***@param$lat1*@param$lng1*@param$lat2*@param$lng2*@returnint*/functiongetDistance($lat1,$lng1,$lat2,$lng2){//将角度转为狐度$radLat1=deg2rad($lat1);//deg2rad()函数将角度转换为弧度$radLat2=deg2rad......