资讯专栏INFORMATION COLUMN

JDK1.8 ArrayList部分源码分析小记

王军 / 2970人阅读

摘要:部分源码分析小记底层数据结构底层的数据结构就是数组,数组元素类型为类型,即可以存放所有类型数据。初始容量大于初始化元素数组新建一个数组初始容量为为空对象数组初始容量小于,抛出异常无参构造函数。

JDK1.8 ArrayList部分源码分析小记 底层数据结构

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

继承与实现关系

ArrayList继承的父类为:AbstractList(抽象类)
实现的接口有:List(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)

友情提示:因为ArrayList实现了RandomAccess接口,所以尽量用for(int i = 0; i < size; i++) 来遍历而不要用Iterator迭代器来遍历,后者在效率上要差一些

public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable
部分属性

类的属性中核心的属性为elementData,类型为Object[],用于存放实际元素,并且被标记为transient,也就意味着在序列化的时候,此字段是不会被序列化的。

友情提示:ArrayList的默认容量为10

    // 缺省容量
    private static final int DEFAULT_CAPACITY = 10;
    // 元素数组(调用指定初始值的构造函数时elementData的长度会变成指定值)
    transient Object[] elementData; 
    // 空对象数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 缺省空对象数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
常用构造函数

友情提示:创建ArrayList时尽量设置初始大小(使用ArrayList(int initialCapacity)构造函数)

    /**
     * ArrayList带容量大小的构造函数
     *
     * @param initialCapacity
     */
    //说明:指定elementData数组的大小,不允许初始化大小小于0,否则抛出异常。
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {// 初始容量大于0
            this.elementData = new Object[initialCapacity];// 初始化元素数组(新建一个数组)
        } else if (initialCapacity == 0) {// 初始容量为0
            this.elementData = EMPTY_ELEMENTDATA;// 为空对象数组
        } else {// 初始容量小于0,抛出异常
            throw new IllegalArgumentException("Illegal Capacity: " +
                    initialCapacity);
        }
    }
    
    /**
     * ArrayList无参构造函数。默认容量是10
     */
    //说明:当未指定初始化大小时,会给elementData赋值为空集合。
    public ArrayList() {
        // 无参构造函数,设置元素数组为空
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
常用函数分析

友情提示
add方法:当容量到达size时进行扩容(add(E e)中先调用了ensureCapacity(size+1)方法,之后将元素的索引赋给elementData[size],而后size自增),扩容为当前容量的0.5倍(若果ArrayList的当前容量为10,那么一次扩容后的容量为15)
set方法:调用set方法时会检验索引是否合法(只校验了上限)(index不能等于size(indexget方法:调用get方法时也会检验索引是否合法(只校验了上限)(index不能等于size(indexremove方法:在移除指定下标的元素时,会把指定下标到数组末尾的元素向前移动一个单位,并且会把数组最后一个元素设置为null,这样是为了方便之后整个数组不被使用时,可以被GC;元素移动时使用的是System.arraycopy()方法

/**
     * 添加元素
     *
     * @param e
     * @return
     */
    public boolean add(E e) {
        //确保elementData数组有合适的大小
        ensureCapacityInternal(size + 1); 
        elementData[size++] = e;
        return true;
    }

    /**
     * 设定指定下标索引的元素值
     *
     * @param index
     * @param element
     * @return
     */
    public E set(int index, E element) {
        // 检验索引是否合法
        rangeCheck(index);
        // 旧值
        E oldValue = elementData(index);
        // 赋新值
        elementData[index] = element;
        // 返回旧值
        return oldValue;
    }

    /**
     * 获取指定下标的元素
     *
     * @param index
     * @return
     */
    //说明:get函数会检查索引值是否合法(只检查是否大于size,而没有检查是否小于0),
    // 值得注意的是,在get函数中存在element函数,element函数用于返回具体的元素 
    public E get(int index) {
        // 检验索引是否合法
        rangeCheck(index);

        return elementData(index);
    }

    // Positional Access Operations

    /**
     * 位置访问操作
     *
     * @param index
     * @return
     */
    //说明:返回的值都经过了向下转型(Object -> E(泛型))
    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

    /**
     * 移除指定下标元素
     *
     * @param index
     * @return
     */
    //说明:remove函数用户移除指定下标的元素
    // 此时会把指定下标到数组末尾的元素向前移动一个单位,并且会把数组最后一个元素设置为null,这样是为了方便之后将整个数组不被使用时,可以会被GC。
    //提醒:元素移动时使用的是System.arraycopy()方法
    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);
        // 赋值为空,有利于进行GC
        elementData[--size] = null; // clear to let GC do its work
        // 返回旧值
        return oldValue;
    }

    /**
     * 在指定下标位置插入元素
     * @param index
     * @param element
     */
    public void add(int index, E element) {
        //检查索引是否合法
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1); 
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);
        elementData[index] = element;
        size++;
    }
其他方法介绍

友情提示:rangeCheckForAdd方法用于add(int index, E element)和addAll(int index, Collection c)方法中检验索引是否合法;rangeCheck方法用于get、set等方法中检验索引是否合法(因为不改变数据结构,故index不能取到size,最大只能取到size-1)

    //检验索引是否合法(只校验了上限)(index不能等于size(index= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    //检验索引是否合法(校验了上限和下限)(index可以等于size(0<=index<=size))
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
扩容策略
    // 确定ArrarList的容量。
    // 若ArrayList的容量不足以容纳当前的全部元素,设置 新的容量=“(原始容量x3)/2 + 1”
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                ? 0
                : DEFAULT_CAPACITY;//默认容量10

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

    //确保elementData数组有合适的大小
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {// 判断元素数组是否为空数组
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);// 取较大值
        }
        //确保elemenData数组有合适的大小
        ensureExplicitCapacity(minCapacity);
    }

    //确保elemenData数组有合适的大小
    private void ensureExplicitCapacity(int minCapacity) {
        // 结构性修改加1
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    //对数组进行扩容
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;// 旧容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);// 新容量为旧容量的1.5倍
        if (newCapacity - minCapacity < 0)// 新容量小于参数指定容量,修改新容量
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)// 新容量大于最大容量
            newCapacity = hugeCapacity(minCapacity);// 指定新容量
        // 拷贝扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
部分方法测试

add方法

public class AddTest {
    @Test
    public void test1(){
        //我们可以看到,在add方法之前开始elementData = {};
        // 调用add方法时会继续调用,直至grow,最后elementData的大小变为10,
        // 之后再返回到add函数,把8放在elementData[0]中
        List lists = new ArrayList();
        lists.add(8);
    }

    @Test
    public void test2(){
        //说明:我们可以知道,在调用add方法之前,elementData的大小已经为6,之后再进行传递,不会进行扩容处理。
        List lists = new ArrayList(6);//elementData.length=6
        lists.add(8);
    }
    
}

rangeCheck方法

public class RangeCheckTest {
    @Test
    public void test() {
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        //该语句报ArrayIndexOutOfBoundsException异常是rangeCheck(index)引发的(index >= size)
        System.out.println(list.get(10));
        //rangeCheck(index)方法只校验上线,该语句报ArrayIndexOutOfBoundsException异常是elementData[index]引发的
        System.out.println(list.get(-1));
        list.remove(-1);//同上

        Object[] a = new Object[]{1, 2, 3};
        System.out.println(a[-1]);
    }
}

indexOf方法

public class IndexOfTest {
    @Test
    public void test(){
        List list = new ArrayList();
        list.add(null);
        list.add(2);
        list.add(2);
        list.add(null);
        System.out.println(list.indexOf(null));//0
        System.out.println(list.indexOf(2));//1
        System.out.println(list.indexOf(3));//-1
    }
}

toArray方法

public class ToArrayTest {
    @Test
    public void test() {
        List list = new ArrayList(10);
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        Object[] array =list.toArray();
        //调用Arrays.copyOf()-->调用System.arraycopy()
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67625.html

相关文章

  • 这几道Java集合框架面试题在面试中几乎必问

    摘要:若遇到哈希冲突,则将冲突的值加到链表中即可。之后相比于之前的版本,之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值默认为时,将链表转化为红黑树,以减少搜索时间。有序,唯一红黑树自平衡的排序二叉树。 本文是最最最常见Java面试题总结系列第三周的文章。主要内容: Arraylist 与 LinkedList 异同 ArrayList 与 Vector 区别 HashMap的底层...

    bigdevil_s 评论0 收藏0
  • java源码

    摘要:集合源码解析回归基础,集合源码解析系列,持续更新和源码分析与是两个常用的操作字符串的类。这里我们从源码看下不同状态都是怎么处理的。 Java 集合深入理解:ArrayList 回归基础,Java 集合深入理解系列,持续更新~ JVM 源码分析之 System.currentTimeMillis 及 nanoTime 原理详解 JVM 源码分析之 System.currentTimeMi...

    Freeman 评论0 收藏0
  • JDK源码(容器篇)

    摘要:三系列用于保存键值对,无论是,还是已弃用的或者线程安全的等,都是基于红黑树。是完全基于红黑树的,并在此基础上实现了接口。可以看到,只有红黑树,且红黑树是通过内部类来实现的。 JDK容器 前言 阅读JDK源码有段时间了,准备以博客的形式记录下来,也方便复习时查阅,本文参考JDK1.8源码。 一、Collection Collection是所有容器的基类,定义了一些基础方法。List、Se...

    Soarkey 评论0 收藏0
  • Java对象内存占用分析

    摘要:对于不同的实现,对象占用的内存空间大小可能不尽相同,本文主要分析中的情况,实验环境为位系统,使用进行结论验证。内存占用这里分析一个只有一组键值对的结构如下首先分析本身的大小。 本文深入分析并验证了不同Java对象占用内存空间大小的情况。对于不同的jvm实现,Java对象占用的内存空间大小可能不尽相同,本文主要分析HotSpot jvm中的情况,实验环境为64位window10系统、JD...

    JouyPub 评论0 收藏0
  • Java集合源码分析系列-(一)ArrayList源码剖析

    摘要:需要注意的是,通过构造函数定义初始量是动态数组的实际大小。带容量的构造函数新建一个容量为的数组默认构造函数,默认为空构造一个包含指定元素的第一个构造方法使用提供的来初始化数组的大小。 前言 今天介绍经常使用的一个Java集合类——ArrayList(基于JDK1.8.0_121)。ArrayList在工作和日常面试中经常被使用或者提到。总的来说,工作中使用ArrayList主要是因为动...

    Miyang 评论0 收藏0

发表评论

0条评论

王军

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<