阅读 224

ArrayList源码分析---JDK1.8

ArrayList源码分析---JDK1.8

ArrayList源码分析---JDK1.8

一. ArrayList的数据结构

二. ArrayList源码分析

①. 继承关系

②. 类中的属性

③. 构造方法

④. 核心方法

1. add(E e)__有四个方法,我仔细分析一个

2. ensureCapacityInternal(size + 1) 确定内部容量的方法

3. calculateCapacity() 主要看list是不是初始的时候是空参构造函数

4. 还是确保明确的容量 ensureExplicitCapacity(int minCapacity)

5. grow(int minCapacity) 扩容机制——重点(看我的注释)

6.hugeCapacity()大容量分配,最大分配 Integer.MAX_VALUE

7.分析一波ArrayList构造函数不传容量的情况

8.分析一波ArrayList构造函数传一个容量的情况或者传0情况

9. remove() 删除方法

10.remove(Object o) 移除list中指定的第一个元素

11.fastRemove(int index) 传入下标进行删除当前index下标位置的元素

12.get(int index) 直接获取下标的位置的值

13.clear 将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉

三. 总结

一. ArrayList的数据结构



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

二. ArrayList源码分析

①. 继承关系



RandomAccess接口:这个是一个标记性接口,通过查看api文档,它的作用就是用来快速随机存取

Serializable接口:实现该序列化接口,表明该类可以被序列化就是能够从类变成字节流传输,然后还能从字节流变成原来的类。

Cloneable接口:实现了该接口,就可以使用Object.Clone()方法了。

ArrayList就继承这个AbstractList类,继承一些通用的方法,然后自己在实现一些自己特有的方法

②. 类中的属性



   //序列号

    private static final long serialVersionUID = 8683452581122892189L;


    //默认初始容量  10

    private static final int DEFAULT_CAPACITY = 10;


    //一个空数组,当用户指定ArrayList容量为0时,用户指定容量为 0 时返回返回该数组

    //ArrayList list1 = new ArrayList(0);

    private static final Object[] EMPTY_ELEMENTDATA = {};



    //ArrayList list = new ArrayList();

     //1. 当用户没有指定 ArrayList() 的容量时(即调用无参构造函数),返回的是该数组==>刚创建一个 ArrayList 时,其内数据量为 0。

     //2. 当用户第一次添加元素时,该数组将会扩容,变成默认容量为 10(DEFAULT_CAPACITY) 的一个数组===>通过  ensureCapacityInternal() 实现

     //3. 它与 EMPTY_ELEMENTDATA 的区别就是:该数组是默认返回的,而后者是在用户指定容量为 0 时返回

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


    //1. 当前数据对象存放地方,当前对象不参与序列化

    //2. 这个关键字最主要的作用就是当序列化时,被transient修饰的内容将不会被序列化

    transient Object[] elementData;


    //ArrayList实际存储的数据数量

    private int size;


    //继承于AbstractList

    //集合数组修改次数的标识

    protected transient int modCount = 0;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

③. 构造方法

无参构造方法_(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)




    //1. 无参构造函数:没有进行传参 当前元素数组直接返回默认 容量空元素数据 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

     //2. 创建一个 空的 ArrayList,此时其内数组缓冲区 elementData = {}, 长度为 0

     //3. 当元素第一次被加入时,扩容至默认容量 10

    public ArrayList() {

        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

    }

1

2

3

4

5

6

有参构造方法1,若参数为0_EMPTY_(ELEMENTDATA)



    /**

     * @param  initialCapacity  初始容量

     * @throws IllegalArgumentException 当初试容量值非法(小于0)时抛出

     */

    //创建一个初试容量的、空的ArrayList arraylist并不是懒加载机制

    public ArrayList(int initialCapacity) {

        if (initialCapacity > 0) {

            this.elementData = new Object[initialCapacity];

        } else if (initialCapacity == 0) {

            // 当出现这个情况 ArrayList list = new ArrayList(0);和直接不传是不一样的

            //传0 是直接赋值 EMPTY_ELEMENTDATA

            //不传 是直接赋值 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

            //初始化容量为0 的时候 直接返回 EMPTY_ELEMENTDATA 空元素数据

            this.elementData = EMPTY_ELEMENTDATA;

        } else {

            throw new IllegalArgumentException("Illegal Capacity: "+

                                               initialCapacity);

        }

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

有参构造方法2_集合添加




    /**

     * @param c 要放入 ArrayList 中的集合,其内元素将会全部添加到新建的 ArrayList 实例中

     * @throws NullPointerException 当参数 c 为 null 时抛出异常

     */

    //创建一个包含collection的ArrayList

    public ArrayList(Collection<? extends E> c) {

        //把集合传化成Object[]数组

        elementData = c.toArray();

        //转化后的数组长度赋给当前ArrayList的size,并判断是否为0

        if ((size = elementData.length) != 0) {

            if (elementData.getClass() != Object[].class)

                // 若c.toArray() 返回的数组类型不是 Object[],则利用 Arrays.copyOf(); 来构造一个大小为 size 的 Object[] 数组

                elementData = Arrays.copyOf(elementData, size, Object[].class);

        } else {

            // 数组长度为0 直接使用空数组

            this.elementData = EMPTY_ELEMENTDATA;

        }

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

④. 核心方法

1. add(E e)__有四个方法,我仔细分析一个




1. add(E e) 默认直接在末尾添加元素


  //增加指定的元素到ArrayList的最后位置

    public boolean add(E e) {

        //确定ArrayList的容量大小  size是当前集合元素的个数

        // 若第一次 size肯定是0 minCapacity=1  +1是未来判断新加进来的元素是否有位置可以存下来

        ensureCapacityInternal(size + 1);

        //确保容量充足 进行元素添加

        elementData[size++] = e;

        return true;

    }

1

2

3

4

5

6

7

8

9

2. ensureCapacityInternal(size + 1) 确定内部容量的方法

2. ensureCapacityInternal(size + 1) 确定内部容量的方法




    //确保内部容量

    private void ensureCapacityInternal(int minCapacity) {

        //calculateCapacity()判断是否走的是空参构造函数 DEFAULTCAPACITY_EMPTY_ELEMENTDATA  将容量初始为10

        //ensureExplicitCapacity判断容量是否要进行扩容 并且modCount++

        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

    }


1

2

3

4

5

6

7

3. calculateCapacity() 主要看list是不是初始的时候是空参构造函数

3. calculateCapacity()判断是否走的是空参构造函数,走空参将容量初始为10




    //DEFAULTCAPACITY_EMPTY_ELEMENTDATA 主要是这个 默认容量空元素数据

    //这个返回主要是看list是不是初始的时候是调用的空参构造函数 ====>ArrayList list = new ArrayList(); DEFAULTCAPACITY_EMPTY_ELEMENTDATA

    private static int calculateCapacity(Object[] elementData, int minCapacity) {

        //如果没有指定初始化容量,第一次调用add()方法,会初始化容量为10

        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

            //直接返回minCapacity默认容量10 和传入的最小容量取最大值

            return Math.max(DEFAULT_CAPACITY, minCapacity);

        }

        //若不是第一次添加直接返回minCapacity 也就是数组的大小+1

        return minCapacity;

    }

1

2

3

4

5

6

7

8

9

10

11

4. 还是确保明确的容量 ensureExplicitCapacity(int minCapacity)

4.还是确保明确的容量 ensureExplicitCapacity(int minCapacity)




    //还是确保明确的容量

    private void ensureExplicitCapacity(int minCapacity) {

        // 将“修改统计数”+1,该变量主要是用来实现fail-fast机制的

        modCount++;

        // 防止溢出代码:确保指定的最小容量 > 数组缓冲区当前的长度

        //若当前集合的最小容量超过数据的长度 需要进行扩容

        //比如第一次添加&&elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ==> minCapacity=10

        if (minCapacity - elementData.length > 0)

            grow(minCapacity);

    }

1

2

3

4

5

6

7

8

9

10

5. grow(int minCapacity) 扩容机制——重点(看我的注释)

5. grow(int minCapacity) 扩容机制——重点




    //扩容,以确保 ArrayList 至少能存储 minCapacity 个元素

    private void grow(int minCapacity) {

        // 计算出老的数组的容量

        int oldCapacity = elementData.length;

        //扩充当前容量为老的数组容量1.5倍

        int newCapacity = oldCapacity + (oldCapacity >> 1);

        // 若扩充后newCapacity 还是小于 添加元素时候传进来的容量 minCapacity

        if (newCapacity - minCapacity < 0)

            //直接将minCapacity直接赋值新的容量 newCapacity

            //若是第一次 newCapacity = minCapacity=10

            newCapacity = minCapacity;

        // 若扩充后的newCapacity 大于最大存储容量,则进行大容量分配

        if (newCapacity - MAX_ARRAY_SIZE > 0)

            newCapacity = hugeCapacity(minCapacity);

        //将数组元素进行copy 长度为 newCapacity

        elementData = Arrays.copyOf(elementData, newCapacity);

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

6.hugeCapacity()大容量分配,最大分配 Integer.MAX_VALUE

6.hugeCapacity()大容量分配,最大分配 Integer.MAX_VALUE




    /**

     * 数组缓冲区最大存储容量

     * - 一些 VM 会在一个数组中存储某些数据--->为什么要减去 8 的原因

     * - 尝试分配这个最大存储容量,可能会导致 OutOfMemoryError(当该值 > VM 的限制时)

     */

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    

    //大容量分配,最大分配 Integer.MAX_VALUE

    private static int hugeCapacity(int minCapacity) {

        //越界直接抛异常

        if (minCapacity < 0)

            throw new OutOfMemoryError();

        //Integer.MAX_VALUE - 8;

        return (minCapacity > MAX_ARRAY_SIZE) ?

            Integer.MAX_VALUE :

            MAX_ARRAY_SIZE;

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

7.分析一波ArrayList构造函数不传容量的情况

7.分析一波ArrayList构造函数不传容量的情况


//1. 不传容量参数,直接走空参构造方法

 List<Integer> lists = new ArrayList<Integer>();

  lists.add(8);

  

  //2. 走空参构造函数

 public ArrayList() {

        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

    }

   //3. 添加元素的时候 进行判断==>空参直接使用默认容量10

   if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

            //直接返回minCapacity默认容量10 和传入的最小容量取最大值

            return Math.max(DEFAULT_CAPACITY, minCapacity);

        }

1

2

3

4

5

6

7

8

9

10

11

12

13



在add方法之前elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA。调用add方法时进行判断初始化默认容量为10,之后再返回到add函数,把8放在elementData[0]中

8.分析一波ArrayList构造函数传一个容量的情况或者传0情况

8.分析一波ArrayList构造函数传一个容量的情况或者传0情况


  //1. 传一个初始化容量参数6

 List<Integer> lists = new ArrayList<Integer>(6);

  lists.add(8);


   //2. 创建一个初试容量的、空的ArrayList arraylist并不是懒加载机制

    public ArrayList(int initialCapacity) {

        if (initialCapacity > 0) {

        //大于0 直接创建一个对象数组

            this.elementData = new Object[initialCapacity];

             //传0 是直接赋值 EMPTY_ELEMENTDATA

        } else if (initialCapacity == 0) {

            // 当出现这个情况 ArrayList list = new ArrayList(0);和直接不传是不一样的

            //传0 是直接赋值 EMPTY_ELEMENTDATA

            //不传 是直接赋值 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

            //初始化容量为0 的时候 直接返回 EMPTY_ELEMENTDATA 空元素数据

            this.elementData = EMPTY_ELEMENTDATA;

        } else {

            throw new IllegalArgumentException("Illegal Capacity: "+

                                               initialCapacity);

        }

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21



我们可以知道,在调用add方法之前,elementData的大小已经为6,之后再进行传递,不会进行扩容处理。

正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。

9. remove() 删除方法

9.remove(int index) 移除指定位置下标的元素




    //移除指定位置下标的元素

    public E remove(int index) {

        //1. 判断索引是否越界

        rangeCheck(index);

        //2. 增加修改的次数

        modCount++;

        //3. 保存要删除的元素为oldValue

        E oldValue = elementData(index);

        //4. 将指定位置index+1往后的元素都向前移动一位,覆盖需要删除的元素

        int numMoved = size - index - 1;

        // 再进行数组copy

        if (numMoved > 0)

            System.arraycopy(elementData, index+1, elementData, index,

                             numMoved);

        // 将最后一个元素置空

        elementData[--size] = null;

        // 返回删除的元素

        return oldValue;

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

10.remove(Object o) 移除list中指定的第一个元素

10.remove(Object o) 移除list中指定的第一个元素




    //移除list中指定的第一个元素

    public boolean remove(Object o) {

        if (o == null) {

            for (int index = 0; index < size; index++)

                if (elementData[index] == null) {

                    //如果包含null这个元素,index 之后的所有元素依次左移一位

                    fastRemove(index);

                    return true;

                }

        } else {

            for (int index = 0; index < size; index++)

                //通过元素 计算出下标

                if (o.equals(elementData[index])) {

                    //如果包含这个元素,index 之后的所有元素依次左移一位

                    fastRemove(index);

                    return true;

                }

        }

        return false;

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

11.fastRemove(int index) 传入下标进行删除当前index下标位置的元素

11.fastRemove(int index) 传入下标进行删除当前index下标位置的元素




    //传入下标进行删除当前index下标位置的元素

    private void fastRemove(int index) {

        modCount++;

        //移动的个数

        int numMoved = size - index - 1;

        //将index后面的元素移动前面来 index位置的元素就是在最后一个位置

        if (numMoved > 0)

            System.arraycopy(elementData, index+1, elementData, index,

                             numMoved);

        //将最后一个元素删除

        elementData[--size] = null;

    }

1

2

3

4

5

6

7

8

9

10

11

12

12.get(int index) 直接获取下标的位置的值

12.get(int index) 直接获取下标的位置的值




    //直接获取下标的位置的值

    public E get(int index) {

    //检查范围

        rangeCheck(index);

        //直接返回

        return elementData(index);

    }

1

2

3

4

5

6

7

13.clear 将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉

13.clear 将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉




    //移除list中的所有元素,这个list表将在调用之后置空

    public void clear() {

        modCount++;

        for (int i = 0; i < size; i++)

            elementData[i] = null;

        size = 0;

    }

1

2

3

4

5

6

7

三. 总结

arrayList可以存放null

arrayList本质上就是一个elementData数组。

arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。

arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全是删除集合中的元素。

arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果

arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。

主要是分析构造参数是否进行了传参,这里要判断是否要使用默认的容量10

补充一个添加元素add整个的流程




补充一个删除元素remove整个的流程



————————————————

版权声明:本文为CSDN博主「戏子zzz」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/weixin_45480785/article/details/116170612


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐