java框架集合List子接口之ArrayList源码剖析

简介: ArrayList使用尾删法时 , 时间复杂度为O(1) , 并且会把最后一个元素置位null , 其它删除时间复杂度为O(N) , 因为会涉及到元素的移动以及元素的遍历ArrsyList是线程不安全的

ArrayList

ArrayList实现了List接口 , 它是有序且可以重复的 , 允许存放所有所有元素 , 包括null , 除了实现List接口之外这个类还提供了一些方法来操作内部存储列表数组的大小 , 这个类大致相当于Vector , 只是它不是同步的 , 同时ArrayList还实现了RandomAccess, Cloneable, java.io.Serializable


RandomAccess

Random是随机的意思,Access是访问的意思,合起来就是随机访问的意思。RandomAccess是一个标记接口 , 用以标记实现的List集合具备快速随机访问的能力 , 那么什么是随机访问的能力呢?其实很简单,随机访问就是随机访问List中的任何一个元素。


所有的List实现都支持随机访问的,只是基于基本结构的不同,实现的速度不同罢了,这里的快速随机访问,那么就不是所有List集合都支持了


ArrayList基于数组实现,天然带下标 ,可以实现常量级的随机访问,复杂度为O(1)


LinkedList基于链表实现,随机访问需要依靠遍历实现,复杂度为O(n)


通过代码的实现也能看出来 , ArrayList实现了RandomAccess接口 , 而LinkedList没有实现


Cloneable

支持拷贝:实现Cloneable接口,重写clone方法、方法内容默认调用父类的clone方法。


浅拷贝(shallowCopy)


如果是基础类型和String类型的变量拷贝之后是独立的,不会随着源变量变动而变 , 但是如果修改了任意一个变量的值 , 对另一个都是有影响的


引用类型拷贝的是引用地址,拷贝前后的变量引用同一个堆中的对象


深拷贝(deepCopy)

基本类型是复制的变量名和值,值变了不影响源变


用类型复制的是变量名和值(引用地址),对象变了,会影响源变量(引用地址是一样的)


String:是不可变对象,重新赋值时,会在常量表新生成字符串(如果已有,直接取他的引用地址),将新字符串的引用地址赋值给栈中的新变量,因此源变量不会受影响


也可以理解为浅拷贝是修改堆内存中的同一个值 , 而深拷贝是修改堆内存中的不同的值


java.io.Serializable

序列化 : 把Java对象转换为字节序列(二进制)的过程


序列化作用 : 在传递和保存对象时.保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中


反序列化 : 把字节序列(二进制)恢复为Java对象的过程


反序列化作用 : 根据字节流中保存的对象状态及描述信息,通过反序列化重建对象


json/xml的数据传递

在数据传输(也可称为网络传输)前,先通过序列化工具类将Java对象序列化为json/xml文件。


在数据传输(也可称为网络传输)后,再将json/xml文件反序列化为对应语言的对象


序列化优点


将对象转为字节流存储到硬盘上,当JVM停机的话,字节流还会在硬盘上默默等待,等待下一次JVM的启动,把序列化的对象,通过反序列化为原来的对象,并且序列化的二进制序列能够减少存储空间(永久性保存对象)。


序列化成字节流形式的对象可以进行网络传输(二进制形式),方便了网络传输。


通过序列化可以在进程间传递对象。


ArrayList属性

// 默认初始容量
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;
// 数组最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

ArryList构造函数

无参构造

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

如果使用无参构造创建ArrayList对象 , 此时我们创建的ArrayList对象中的elementData中的长度是1,size是0 , 当第一次添加元素的时候 , elementData才会变成默认长度10


有参构造

 

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    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);
        }
    }

如果传入参数,则代表指定ArrayList的初始数组长度,传入参数如果是大于等于0,则使用用户的参数初始化,如果用户传入的参数小于0,则抛出异常

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    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;
        }
    }

将collection对象转换成数组,然后将数组的地址赋给elementData。


更新size的值,同时判断size的大小,如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData


如果size的值大于0,则执行Arrays.copy方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。


注意:this.elementData = arg0.toArray(); 这里执行的简单赋值是浅拷贝,所以要执行Arrays.copy 做深拷贝


add(E e)方法源码

这种情况下默认使用尾插法 , 时间复杂度O(1) , 如果需要扩容 , 则使用Arrays.copy()进行深拷贝 , 以此来生成新的数组 , 然后把旧数组的值赋值给新数组 , 并且如果此时数组的长度+1小于默认容量 , 那么就会初始化一个容量为10的数组 , 并不是在new ArrayList<>()的时候就会生成默认容量的数组


 /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        // 判断是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 从默认容量和当前ArrayList的size + 1选一个最大的最为扩容阈值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
    private void ensureExplicitCapacity(int minCapacity) {
        // 将修改次数(modCount)自增1
        modCount++;
        // overflow-conscious code
        // 判断是否需要扩容 , 如果当前容量减去数组容量大于0 , 表示需要扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
        /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        // 旧的容量
        int oldCapacity = elementData.length;
        // 新容量等于旧容量 + 旧容量左移1位
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            // 如果新容量 - 需要扩容阈值 < 0 那么新容量就使用扩容阈值
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            // 如果新容量 - 数组最大长度 > 0 , 那么就需要另外判断
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

旧数组值赋值给新数组之后可能会出现三种情况 :


如果旧数组之前经过多次年轻代的GC(yongGC)没有被清理掉的话 ,旧数组就会进入老年代 , 那么生成新数组之后 ,旧数组会被老年代GC掉


如果旧数组经过多次yongGC , 但是没有符合进入老年代的条件及每次yongGC都会在Survivor 0 ,Survivor 1进行移动 , 每移动一次同时记录次数, 达到15次 , 才会被放入老年代 ,这个时候就会被yongGC


如果新数组分配的和旧数组是同一块地址 ,这个时候不需要进行回收


add(int index, E element)源码

如果使用的这个add方法 , 那么将会在数组指定位置插入一个新元素 , 其它元素向后移动 ,此时时间复杂度为O(N) , 扩容机制和add(E e)方法是相同的

    public void add(int index, E element) {
        // 如果index大于当前ArrayList的size , 或者小于0 , 则抛异常
        rangeCheckForAdd(index);
        // 判断是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 将新的数据内容存放到数组的指定位置(index)上
        elementData[index] = element;
        size++;
    }

get(int index)源码

数组的查询是通过一个公式来计算的 :a[n] = 起始地址 + (n * 字节数) , n表示的就是下标 , 比如我们此时要寻找下边为2的元素 , 那么通过公式计算 , 100 + (2*4) , 那么找到的就是地址为108的元素 , 也就是说5 , 因为它有一个公式 , 经过计算 , 可以很快的找到这个元素的地址 , 所以它的查询的时间复杂度是o(1) , 因此使用get方法随机访问元素的时候 , 时间复杂度为O(1)

    public E get(int index) {
        // 检查下标是否越界
        rangeCheck(index);
        return elementData(index);
    }
    E elementData(int index) {
        // 返回下标对应的元素
        return (E) elementData[index];
    }

contains方法

调用indexOf方法,遍历数组中的每一个元素作对比,如果找到对于的元素,则返回true,没有找到则返回false

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    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;
    }

remove(int index)源码

根据下标删除的情况下时间复杂度分为几种情况考虑 : 如果使用尾删法及删除最后一个元素 , 那个时间复杂度为O(1) , 不涉及到其他元素的移动 , 否则时间复杂度为O(N) , 将会涉及到其他元素的移动

 public E remove(int index) {
        // 检查下标是否越界
        rangeCheck(index);
        // 修改次数 + 1
        modCount++;
        // 根据下标获取需要修改的值
        E oldValue = elementData(index);
        // 判断是否需要移动数组元素
        int numMoved = size - index - 1;
        if (numMoved > 0)
            // 使用System.arraycopy 将需要插入的位置(index)后面的元素统统往前移动一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 不需要移动元素的话 , 把最后一位置为空
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }

remove(Object o)源码

如果使用这种方法进行删除元素 , 那么时间复杂度为O(N) , 涉及到元素的比阿尼寻找以及移动


 

    public boolean remove(Object o) {
        // 循环遍历所有对象,得到对象所在索引位置,然后调用fastRemove方法,执行remove操作
        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;
    }
    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()源码

添加操作次数(modCount),将数组内的元素都置空,等待垃圾收集器收集,不减小数组容量。

   public void clear() {    
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }

subList(int fromIndex, int toIndex)源码

我们看到代码中是创建了一个ArrayList 类里面的一个内部类SubList对象,传入的值中第一个参数时this参数,其实可以理解为返回当前list的部分视图,真实指向的存放数据内容的地方还是同一个地方,如果修改了sublist返回的内容的话,那么原来的list也会变动。

public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

iterator()源码

interator方法返回的是一个内部类,由于内部类的创建默认含有外部的this指针,所以这个内部类可以调用到外部类的属性

public Iterator<E> iterator() {
        return new Itr();
    }


一般我们调用interator之后会进行集合的遍历 , 这里使用next做遍历的时候有个需要注意的地方,就是调用next的时候,可能会引发ConcurrentModificationException,当修改次数,与期望的修改次数(调用iterator方法时候的修改次数)不一致的时候,会发生该异常,详细我们看一下代码实现

    @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];
    }
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

expectedModCount这个值是在用户调用ArrayList的iterator方法时候确定的,但是在这之后用户add,或者remove了ArrayList的元素,那么modCount就会改变,那么这个值就会不相等,将会引发ConcurrentModificationException异常,这个是在多线程使用情况下,比较常见的一个异常。


System.arraycopy 方法

参数 说明

src 原数组

srcPos 原数组

dest 目标数组

destPos 目标数组的起始位置

length 要复制的数组元素的数目

Arrays.copyOf方法

参数

说明

original 要复制的数组

newLength 要返回副本的长度

newType 要返回副本的类型

其实Arrays.copyOf底层也是调用System.arraycopy实现的源码如下:

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

小结

ArrayList底层是基于动态数组实现的 , 可以进行动态扩容


ArrayList自己实现了序列化和反序列化方法 , 因为它实现了writeObject和readObject方法


ArrayList在编码时如果知道数据比较多 , 那么可以提前传入容量值 , 避免进行频繁扩容


ArrayList使用尾插法的时间复杂度为O(1) , 指定位置插入时间复杂度为O(N)


ArrayList支持随机访问 , 其时间复杂度为O(1)


ArrayList使用尾删法时 , 时间复杂度为O(1) , 并且会把最后一个元素置位null , 其它删除时间复杂度为O(N) , 因为会涉及到元素的移动以及元素的遍历


ArrsyList是线程不安全的

相关文章
|
14天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
50 7
|
25天前
|
Java 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
25天前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
25天前
|
数据采集 人工智能 Java
Java产科专科电子病历系统源码
产科专科电子病历系统,全结构化设计,实现产科专科电子病历与院内HIS、LIS、PACS信息系统、区域妇幼信息平台的三级互联互通,系统由门诊系统、住院系统、数据统计模块三部分组成,它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。
28 4
|
6天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
52 13
|
20天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
50 12
|
14天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
20天前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
31 4
|
16天前
|
人工智能 移动开发 安全
家政上门系统用户端、阿姨端源码,java家政管理平台源码
家政上门系统基于互联网技术,整合大数据分析、AI算法和现代通信技术,提供便捷高效的家政服务。涵盖保洁、月嫂、烹饪等多元化服务,支持多终端访问,具备智能匹配、在线支付、订单管理等功能,确保服务透明、安全,适用于家庭生活的各种需求场景,推动家政市场规范化发展。
|
6月前
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
1015 1