吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1771|回复: 9
收起左侧

[Java 转载] ArrayList源码阅读分析

[复制链接]
独怜 发表于 2021-10-13 13:54
本帖最后由 独怜 于 2021-10-13 14:00 编辑

ArrayList源码阅读分析

1.1 ArrayList介绍

image-20211012160617267

ArrayList一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。

ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供<span style="color:red;font-weight:bold"><u>快速访问功能</u></span>的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。

ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList

1.2 ArrayList源码

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity. 
     * 默认容量 10
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     * 空数组,如果传入的容量为0时使用
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     * 空数组,传传入容量时使用,添加第一个元素的时候会重新初始为默认容量大小
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     * 存储元素的数组
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     * 集合中元素的个数
     */
    private int size;

1.2.1 ArrayList构造方法

(1).ArrayList()
  1. 如果没有传入初始容量,则使用空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  2. 使用这个数组是在添加第一个元素的时候会扩容到默认大小10
/**
*  使用 new ArrayList() 创建时默认使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数
**/
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 }
(2). ArrayList(int initialCapacity)
public ArrayList(int initialCapacity) {
            //传入的初始容量大于0,初始化elementData数组对应的大小容量
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        }
            //传入的初始容量等于0,使用一个空数组 EMPTY_ELEMENTDATA
            else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
             // 如果传入的初始容量小于0,抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
}
(3). ArrayList(Collection<? extends E> c)
public ArrayList(Collection<? extends E> c) {
            // 将集合先转换成数组
        Object[] a = c.toArray();
            // 将size赋值为转换后数组的长度且判断转换后数组的长度是否等于0
        if ((size = a.length) != 0) {
            //判断传入的元素是否为ArratList类型,如果是,则直接将elementData赋值为传入的集合转换后的数组
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                // 如果传入的是不是ArrayList类型,重新拷贝成Object[].class类型
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // 传入的集合为一个空集合,初始化一个空数组 EMPTY_ELEMENTDATA
            elementData = EMPTY_ELEMENTDATA;
        }
}

1.2.2 ArrayList添加元素(增)

(1). add(E e)

添加元素到末尾,平均时间复杂度为O(1)。

 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 void ensureExplicitCapacity(int minCapacity) {
    //modCount 为一个全局性变量,记录结构性改变的次数
    modCount++;

    //当数组大小大于此时数组中元素个数,进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

//判断是否是第一次添加数据,如果为第一次,则初始化默认大小10,反之则返回当前数组下标
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 如果是空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就初始化为默认大小10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

//ArrayList 扩容机制
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    //每次扩容为之前容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果新容量发现比需要的容量还小,则以需要的容量为准
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果新容量已经超过最大容量了,则使用最大容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 以新容量拷贝出来一个新数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}
(2). add(int index, E element)

添加元素到指定位置,平均时间复杂度为O(n)。

public void add(int index, E element) {
    // 检查索引是否越界
    rangeCheckForAdd(index);
    //初始化容量判断
    ensureCapacityInternal(size + 1);  // Increments modCount!!

    // 将inex及其之后的元素往后挪一位,则index位置处就空出来了
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 将元素插入到index的位置
    elementData[index] = element;

    // 大小增1
    size++;
}
/**
* 检查索引是否越界
**/
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
(3). addAll(Collection<? extends E> c)

两个集合的第一个并集

public boolean addAll(Collection<? extends E> c) {
    // 将集合先转换成数组
    Object[] a = c.toArray();
    // 获取集合转换成数组的长度
    int numNew = a.length;
    // 检查是否需要扩容 (可跟踪查看add(E e)方法)
    ensureCapacityInternal(size + numNew);  // Increments modCount
    // 将c中元素全部拷贝到数组的最后
    System.arraycopy(a, 0, elementData, size, numNew);
    // 大小增加c的大小
    size += numNew;
    // 如果c不为空就返回true,否则返回false
    return numNew != 0;
}
(4). addAll(int index, Collection<? extends E> c)

在指定索引位置位置添加集合元素

两个集合的并集。

public boolean addAll(int index, Collection<? extends E> c) {
           // 检查索引是否越界
    rangeCheckForAdd(index);
        // 将集合先转换成数组
    Object[] a = c.toArray();
    // 获取集合转换成数组的长度
    int numNew = a.length;
    // 检查是否需要扩容 (可跟踪查看add(E e)方法)
    ensureCapacityInternal(size + numNew);  // Increments modCount
        // 如果index不是最后一位,则将index之后的元素往后挪numNew个位置,将从numMoved的位置空出放入传入的元素
    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);
        // 将c中元素全部拷贝到数组
    System.arraycopy(a, 0, elementData, index, numNew);
    // 大小增加c的大小
    size += numNew;
    // 如果c不为空就返回true,否则返回false
    return numNew != 0;
}

// 判断索引是否越界
private void rangeCheckForAdd(int index) {
    if (index < 0 || index > this.size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

1.2.3 ArrayList删除元素(删)

(1). remove(int index)

删除指定索引位置的元素,时间复杂度为O(n)。

public E remove(int index) {    //检查索引是否越界    rangeCheck(index);        //modCount 为一个全局性变量,记录结构性改变的次数    modCount++;    //获取索引位置元素    E oldValue = elementData(index);        // 如果index不是最后一位,则将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;}
(2). remove(Object value)

删除指定元素值的元素,时间复杂度为O(n)。

public boolean remove(Object o) {    //如果要删除的元素为null     if (o == null) {        // 遍历整个数组,找到元素第一次出现的位置,并将其快速删除        for (int index = 0; index < size; index++)              // 如果要删除的元素为null,则以null进行比较,使用==            if (elementData[index] == null) {                fastRemove(index);                return true;            }    } else {         // 遍历整个数组,找到元素第一次出现的位置,并将其快速删除        for (int index = 0; index < size; index++)             // 如果要删除的元素不为null,则进行比较,使用equals()方法            if (o.equals(elementData[index])) {                fastRemove(index);                return true;            }    }    return false;}// 快速删除private void fastRemove(int index) {    //modCount 为一个全局性变量,记录结构性改变的次数    modCount++;        // 如果index不是最后一位,则将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}
(3). removeAll(Collection<?> c)

从此列表中移除包含在指定集合中的所有元素

public boolean removeAll(Collection<?> c) {    //在运行时对对象做非null检查    Objects.requireNonNull(c);    //批量删除    return batchRemove(c, false);}/** * 批量删除元素 * complement为true表示删除c中不包含的元素 * complement为false表示删除c中包含的元素*/private boolean batchRemove(Collection<?> c, boolean complement) {    final Object[] elementData = this.elementData;    // 使用读写两个指针同时遍历数组    // 读指针每次自增1,写指针放入元素的时候才加1    // 这样不需要额外的空间,只需要在原有的数组上操作就可以了    int r = 0, w = 0;    boolean modified = false;    try {        // 遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准)        for (; r < size; r++)            if (c.contains(elementData[r]) == complement)                elementData[w++] = elementData[r];    } finally {        // Preserve behavioral compatibility with AbstractCollection,        // even if c.contains() throws.        // 正常来说r最后是等于size的,除非c.contains()抛出了异常        if (r != size) {             // 如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后            System.arraycopy(elementData, r,                             elementData, w,                             size - r);            w += size - r;        }        if (w != size) {             // 将写指针之后的元素置为空,帮助GC            // clear to let GC do its work            for (int i = w; i < size; i++)                elementData[i] = null;            modCount += size - w;            // 新大小等于写指针的位置(因为每写一次写指针就加1,所以新大小正好等于写指针的位置)            size = w;            // 有修改返回true            modified = true;        }    }    return modified;}
(4). clear()

清空列表中的元素,但是不修改列表的容量大小

public void clear() {     //modCount 为一个全局性变量,记录结构性改变的次数    modCount++;    // clear to let GC do its work    // 将数组内的元素都置空,等待垃圾收集器收集,不减小数组容量。    for (int i = 0; i < size; i++)        elementData[i] = null;    size = 0;}

1.2.4 ArrayList修改元素(改)

(1) set(int index)

根据索引修改元素

public E set(int index, E element) {    // 检查索引是否越界    rangeCheck(index);        // 根据索引取出元素    E oldValue = elementData(index);    // 根据索引修改数组元素    elementData[index] = element;    // 返回修改之前的值    return oldValue;}//检查索引是否越界 private void rangeCheck(int index) {    if (index < 0 || index >= this.size)        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}

1.2.5 ArrayList取出元素(查)

(1). get(int index)

获取指定索引位置的元素,时间复杂度为O(1)。

//根据索引取出元素public E get(int index) {    //(1) 检查索引是否越界    rangeCheck(index);    //(2)返回索引位置处的元素;    return elementData(index);}// 判断索引是否越界//(1)检查索引是否越界,这里只检查是否越上界,如果越上界抛出IndexOutOfBoundsException异常,如果越下界抛出的是ArrayIndexOutOfBoundsException异常。private void rangeCheck(int index) {    if (index >= size)        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}//根据索引返回元素E elementData(int index) {    return (E) elementData[index];}
(2). retainAll(Collection<?> c)

求两个集合的交集

public boolean retainAll(Collection<?> c) {    //在运行时对对象做非null检查    Objects.requireNonNull(c);    // 调用批量删除方法,这时complement传入true,表示删除不包含在c中的元素    return batchRemove(c, true);}/** * 批量删除元素 * complement为true表示删除c中不包含的元素 * complement为false表示删除c中包含的元素*/private boolean batchRemove(Collection<?> c, boolean complement) {    final Object[] elementData = this.elementData;    // 使用读写两个指针同时遍历数组    // 读指针每次自增1,写指针放入元素的时候才加1    // 这样不需要额外的空间,只需要在原有的数组上操作就可以了    int r = 0, w = 0;    boolean modified = false;    try {        // 遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准)        for (; r < size; r++)            if (c.contains(elementData[r]) == complement)                elementData[w++] = elementData[r];    } finally {        // Preserve behavioral compatibility with AbstractCollection,        // even if c.contains() throws.        // 正常来说r最后是等于size的,除非c.contains()抛出了异常        if (r != size) {             // 如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后            System.arraycopy(elementData, r,                             elementData, w,                             size - r);            w += size - r;        }        if (w != size) {             // 将写指针之后的元素置为空,帮助GC            // clear to let GC do its work            for (int i = w; i < size; i++)                elementData[i] = null;            modCount += size - w;            // 新大小等于写指针的位置(因为每写一次写指针就加1,所以新大小正好等于写指针的位置)            size = w;            // 有修改返回true            modified = true;        }    }    return modified;}
(3). indexOf(Object o)

查找元素在列表中第一次出现的索引位置

public int indexOf(Object o) {    // 判断元素是否为null,如果为null则查找null第一次出现在列表中的位置    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;    }    // 查找不到该元素则返回-1    return -1;}
(4). lastIndexOf(Object o)

查找元素在列表中最后一次出现的索引位置

public int lastIndexOf(Object o) {    if (o == null) {        // 倒序查找最后一次出现时的位置        for (int i = size-1; i >= 0; i--)            if (elementData[i]==null)                return i;    } else {        for (int i = size-1; i >= 0; i--)            if (o.equals(elementData[i]))                return i;    }    // 查找不到该元素则返回-1    return -1;}
(5). contains(Object o)

查找列表是否包含该元素

public boolean contains(Object o) {    // 列表中存在该元素则返回该元素第一次出现的索引,否则返回-1    return indexOf(o) >= 0;}

1.3 ArrayList总结

(1)ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容;

(2)ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);

(3)ArrayList添加元素到尾部极快,平均时间复杂度为O(1);

(4)ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);

(5)ArrayList从尾部删除元素极快,时间复杂度为O(1);

(6)ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n);

(7)ArrayList支持求并集,调用addAll(Collection<? extends E> c)方法即可;

(8)ArrayList支持求交集,调用retainAll(Collection<? extends E> c)方法即可;

(7)ArrayList支持求单向差集,调用removeAll(Collection<? extends E> c)方法即可;

1.4 ArrayList常见面试题

  1. ArrayList是什么?可以用来干嘛?

    ArrayList就是有序的动态数组列表,主要⽤来装载数据,它的主要底层实现是数组Object[]查询效率高,增删效率低
  2. ArrayList与LinkedList的区别?

    1、ArrayList基于动态数组实现的非线程安全的集合;LinkedList基于链表实现的非线程安全的集合。2、对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止。3、新增和删除元素,一般LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。4、LinkedList集合不支持 高效的随机随机访问(RandomAccess)5、ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间(扩容),而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
  3. ArrayList 的默认长度

    默认长度为10当我们第一次向数组中添加数据的时候才会初始化数组,相当于懒加载
  4. ArrayList是怎么扩容的?

    再add(E e)添加新数据的时候会进行一个判断,当我们添加的数据超过数组的长度时,数组就会进行扩容这里分为两步:    1.先创建一个原数组长度1.5倍的新数组        1.7: 是原来数组的1.5倍 + 1        1.8: 使用位移运算符向右一位,效率要高很多    2.然后再把旧的数组copy到新的数组
  5. ArrayList插入、删除、查询元素的时间复杂度各是多少?

    add(E e)方法添加元素到末尾,平均时间复杂度为O(1)。add(int index, E element)方法添加元素到指定位置,平均时间复杂度为O(n)。remove(int index)方法删除指定索引位置的元素,时间复杂度为O(n)。remove(Object o)方法删除指定元素值的元素,时间复杂度为O(n)。get(int index)方法获取指定索引位置的元素,时间复杂度为O(1)。总结:总的来说,不论是删除还是新增,其实本质上都是移动位置,当指定位置新增的时候,新增的索空出,后面向后移一位,然后赋值,当删除时,也是一样,将要删除的索引下标的值置为null。 (增删慢,查询快)
  6. ArrayList的遍历和LinkedList遍历性能⽐较如何?

    ArrayList要比LinkedList快得多,ArrayList遍历最大的优势在于内存的连续性,CPU的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。
  7. 为啥线程不安全还使用ArrayList呢?

    因为在我们正常得使用场景中,都是用来查询的,不会涉及太频繁的增删,如果涉及到频繁的增删,可以使用LinkedList,如果需要线程安全的就是可以使用Vector,CopyOrWriteArrayList
  8. ArrayList的遍历如何?

    ArrayList遍历最大的优势在于内存的连续性,CPU的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。



免费评分

参与人数 3吾爱币 +9 热心值 +3 收起 理由
Ski615753 + 1 + 1 我很赞同!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
niebaohua + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

youle999 发表于 2021-10-13 14:28
感谢大神分享。
龙小 发表于 2021-10-13 14:29
xiaoaiai2468 发表于 2021-10-13 14:48
momo2021 发表于 2021-10-13 19:38
楼主发篇LinkedList解析的帖子吧
 楼主| 独怜 发表于 2021-10-14 14:53
momo2021 发表于 2021-10-13 19:38
楼主发篇LinkedList解析的帖子吧

整理完了就发出来
 楼主| 独怜 发表于 2021-10-14 17:48
momo2021 发表于 2021-10-13 19:38
楼主发篇LinkedList解析的帖子吧

LinkedList看下这个哈, https://www.52pojie.cn/forum.php ... ;extra=#pid40325053
angel_bai 发表于 2021-11-12 09:10
谢谢分享
he183137 发表于 2021-11-12 09:45
很详细,感谢分享
xiaocai66 发表于 2021-11-16 10:48
学习到了,谢谢大佬分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-2 09:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表