ArrayList源码分析

空空道人
• 阅读 999

ArrayList结构和派系

ArrayList源码分析

派生关系

Iterable

 
 集合的最上级接口, 定义了spliterator,iterator和实现了forEach函数

Collection


所有集合的父级接口, 定义了通用函数 

AbstractCollection


实现了Collection中大量函数, 除了特定的几个函数iterator和size之外的函数 

AbstractList


继承了AbstractCollection, 实现了List接口的抽象类, 它实现了List中除size(), get(int location)之外的函数
和AbstractCollection相比, 实现了iterator()接口  

RandomAccess


提供了随机访问(随机查找)功能, 在ArrayList中通过元素的序号快速获取元素对象, 这就是快速随机访问(随机查找)  

List


List有序队列接口, 提供了一些通过下标访问元素的函数.    
List中每一个元素都有一个索引, 第一个元素是0, 往后每次+1

数据结构

ArrayList数据结构是数组
通过一定的算法逻辑动态扩容数组的长度, 可以理解为ArrayList底层是一个动态数组
与java原生的数组相比, 它的容量能动态增长  
内存需要连续的空间保证
除去尾部位置元素的添加, 删除操作, 其余都涉及到位置移动
随机查找效率快(下标搜寻)

初始化

ArrayList源码设计的优雅之道
new ArrayList的时候, 并不会真的创建长度10的对象数组
什么时候往里面添加元素了,什么时候才会完成数组的创建

初始化源码

    private static final long serialVersionUID = 8683452581122892189L;
  
    private static final int DEFAULT_CAPACITY = 10;
 
    private static final Object[] EMPTY_ELEMENTDATA = {};

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    transient Object[] elementData; // non-private to simplify nested class access

    private int size;

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

详细分析

serialVersionUID


用来验证版本一致性的字段, 反序列化时会效验

DEFAULT_CAPACITY


默认容量, 为10 

EMPTY_ELEMENTDATA


空对象数组

DEFAULTCAPACITY_EMPTY_ELEMENTDATA


默认容量空对象数组

elementData


动态数组的空间大小, 包含空元素

size


动态数组的实际大小, 记录数组里有多少元素, 不记录空元素

ArrayList()


无参构造
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}
把默认容量空对象数组赋值给elementData, 还是空
无参构造创建动态数组时, size为0, elementData为0

ArrayList(int initialCapacity)


带参构造
参数大于0时
    this.elementData = new Object[initialCapacity];
    创建一个大小为initialCapacity的Object数组赋给elementData
参数小与于0时
    this.elementData = EMPTY_ELEMENTDATA;
    开一个长度为10的空间
参数小与于0时
    抛异常

总结

代码测试

    public static void main(String[] args) throws Exception {
        ArrayList  arr = new ArrayList();
        Field field = arr.getClass().getDeclaredField("elementData");
        field.setAccessible(true);
        Object[] o = (Object[]) field.get(arr);
        System.out.println(arr.size());
        System.out.println(o.length);

        arr.add("1");
        field = arr.getClass().getDeclaredField("elementData");
        field.setAccessible(true);
        o= (Object[]) field.get(arr);
        System.out.println(arr.size());
        System.out.println(o.length);

        for (int i = 0; i < 16; i++) {
            arr.add(i);
        }
        field = arr.getClass().getDeclaredField("elementData");
        field.setAccessible(true);
        o= (Object[]) field.get(arr);
        System.out.println(arr.size());
        System.out.println(o.length);
    }

输出

0
0
1
10
17
22

初始化总结

ArrayList  arr = new ArrayList();
elementData为0
size为0

ArrayList  arr = new ArrayList(1);
elementData为0
size为1, 走了带参构造, 参数为几, 空间为几, ArrayList(int initialCapacity)   

add("test");//添加元素源码分析会讲为什么空间为10
elementData为10 
size为1
如果第一次添加元素数量为11, 基于动态扩容1.5倍, 它的长度就是15

添加元素

添加元素源码

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }    

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

详细分析

添加一个元素1-10个元素时

    add(E e)方法 //以无参初始化(ArrayList arr = new ArrayList();), 并调用add(E e)添加一个元素为例
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    
    进ensureCapacityInternal(size + 1);此时传参为1 (0+1=1)
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
   
    进calculateCapacity, 判断elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA是否成立
    public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
    条件成立, 因为无参初始化方法已经赋过值
    进if, 判断10和参数哪个大, 10大, 返回10
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
  
    此时返回ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); 这里, 参数是10
    10-10不大于0, 没走到动态扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
目前是elementData(空间长度为10), size为1(size不记录空元素)

添加元素超出10的情况, 动态扩容

    添加第11个元素时
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    此时minCapacity为11
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
   
    不走if, 返回11, 则ensureExplicitCapacity(11)
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
   
    11-10大于0, 进入动态扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
         // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    进grow(minCapacity); 动态扩容方法
    private void grow(int minCapacity) {
        老空间为10
        int oldCapacity = elementData.length;
        新空间=10+(10>>1), 位运算10的二进制位为1010, 
        右移动1位二进制为101, 10进制为5, 所以新空间为15=10+5
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        如果新空间-11小于0,则新空间为11 
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        如果新空间大于0, 则赋值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity); //下面讲这个方法
        拷贝一个新的数组和长度的集合,赋值给我们的对象数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    
    MAX_ARRAY_SIZE为Integer.MAX_VALUE-8 (int最大值-8)
    如果要开的空间大于MAX_ARRAY_SIZE, 就把int最大值-8赋值给它
    如果不大于MAX_ARRAY_SIZE,则把MAX_ARRAY_SIZE赋值给它
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    
    
    官方注释对于-8的介绍
        这8bit里存了要分配最大数组的大小(数组下标???)
        虚拟机中在数据中保留的标题字 (既数组形状判断是否为数组,对象是否同步,数组大小, 不知道是否包含了对象类型的类信息指针???)
        尝试分配更大的数组会抛异常, 其实大小还是支持到了Integer.MAX_VALUE
    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

fail-fast失败原因(modCount++)

之前介绍源码的时候把modCount++跳过了, 接下来解释下, 在ArrayList源码里有个监听
源码

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        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];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

在每次做集合元素操作时,都会改变modCount,在迭代的时候,会把modCount赋值给expectedModCount,      
如果在迭代过程中删除元素,就会修改modCount,但是迭代器在过程中不会同步expectedModCount,  
 每次迭代会会比较是否相等
 就是说在使用迭代器的时候, 不能改变元素, 但是可以使用迭代器的方法去改变

查找元素

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    E elementData(int index) {
        return (E) elementData[index];
    }
    
判断下标是否越界
复杂度O(1), 直接根据下标位置返回元素

删除元素

删除元素源码

    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; // clear to let GC do its work

        return oldValue;
    }
    
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
                                        
    src - 源数组。
    srcPos - 源数组中的起始位置。
    dest - 目标数组。
    destPos - 目的地数据中的起始位置。
    length - 要复制的数组元素的数量。

总结

将指定源数组中的数组从指定位置复制到目标数组的指定位置

ArrayList源码分析

    比如我们有个数组
    值为 a b c d e f g h i g k
    下标 0 1 2 3 4 5 6 7 8 9 10
    如果删除f
    rangeCheck(index); //效验下标越界, index=5
    int numMoved = size - index - 1; //numMoved= 4
    调用System.arraycopy(elementData, index+1, elementData, index, numMoved);

从原数组下标从6开始copy到新数组从下标从5开始的后面

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这