【抽丝剥茧】解析ArrayList源码

2024-02-23 07:20

本文主要是介绍【抽丝剥茧】解析ArrayList源码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、什么是ArrayList?
  • 二、ArrayList与LinkedList的对比
  • 三、4个默认值
    • 1.默认初始容量
    • 2.指定容量为0的时候,返回的空数组
    • 3.未指定容量时,返回的空数组
    • 4.数组大小size的最大值
  • 四、2个基本属性
    • 1.元素数据
    • 2.数组大小(包含元素的数量)
  • 五、3个构造函数
    • 1.无参构造函数
    • 2.指定初始容量的构造函数
    • 3.带Collection集合的构造函数
  • 六、N个方法 公共API
    • 1.⭐根据下标获取元素 get(index)
    • 2.在指定位置塞入元素 set(index, element)
    • 3.⭐添加元素 add(E e)
      • 流程图:
    • 4.在指定位置添加元素 add(int index, E element)
    • 5.移除指定位置的元素 remove(int index)
    • 6.移除数组中第一次出现的指定元素 remove(Object o)
    • 7.清除数据 clear()
    • 8.修剪容量 trimToSize()
    • 9.获取元素数量 size()
    • 10.判断列表是否为空 isEmpty()
    • 11.判断列表中是否包含某元素 contains(Object o)
    • 12.查询指定元素在列表中出现的第一个索引 indexOf(Object o)
    • 13.查询指定元素在列表中出现的最后一个索引 lastIndexOf(Object o)
    • 14.复制列表 clone()
    • 15.列表转化为数组 toArray()
    • 16.列表复制到指定数组 toArray(T[] a)
    • 17.将指定集合里的所有元素添加到列表的末尾 addAll(Collection<? extends E> c)
    • 18.将指定集合里的所有元素添加到列表的指定位置 addAll(int index, Collection<? extends E> c)
    • 19.从此列表中删除包含在指定集合中的所有元素 removeAll(Collection<?> c)
    • 20.从此列表中删除不包含在指定集合中的所有元素 retainAll(Collection<?> c)
    • 21.⭐分割列表 subList(int fromIndex, int toIndex)


一、什么是ArrayList?

  • 基于数组的实现,是一个“动态数组”
    我们知道数组的容量是固定的,不可改变,创建时指定容量大小。而ArrayList是会随着存入数据的增加而进行扩容。
  • 由于实现了List接口,又可称为“数组列表”,实现了Collection接口,又可称为“数组集合”。

二、ArrayList与LinkedList的对比

 ArrayList是基于数组的实现,LinkedList是基于链表的实现,他们的区别基本上也取决于数组和链表的区别。
  • 数组带下标,所以支持随机访问,且速度快,链表不支持。
  • 数组遍历时指针在数组上移动即可,查询速度快;而链表遍历时需要一个个节点逐一查找,所以查询慢。
  • 数组增删数据时,可能会移动其它数据,比如在头部插入一个新数据,下面的数据都需要往后移,所以增删速度慢;而链表在内存中是散列的,增删数据时,只需要改变其中一个节点对下一个节点的引用即可,所以增删快。

区别:
ArrayList 底层是数组,查询快、增删慢,有初始容量(默认10),容量满了,需要进行扩容
LinkedList 底层是链表,查询慢,增删快,无初始容量,默认为0,无扩容操作

三、4个默认值

1.默认初始容量

private static final int DEFAULT_CAPACITY = 10;

2.指定容量为0的时候,返回的空数组

private static final Object[] EMPTY_ELEMENTDATA = {};
new ArrayList(0);

3.未指定容量时,返回的空数组

private static final Object[] EMPTY_ELEMENTDATA = {};
new ArrayList();
此处区别与上面指定容量为0时的空数组,第一次调用add时如果未指定容量则会设置默认容量10,而如果是设置了初始容量为0,第一次add时,只会将容量变成1;

4.数组大小size的最大值

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
int最大值减8,超过这个数,可能会内存溢出,超过某些虚拟机的限制。

四、2个基本属性

1.元素数据

transient Object[] elementData;
elementData是实际存放数据的地方

2.数组大小(包含元素的数量)

private int size;

五、3个构造函数

1.无参构造函数

public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}
使用默认空数组

2.指定初始容量的构造函数

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);}}
阿里巴巴代码规范提示,需指定初始容量,一般用到几个就写几个或者稍大一点。

3.带Collection集合的构造函数

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;}}
先将传入集合c转化为数组元素,toArray,如果c为空,抛出空指针异常。
再将数组复制到elementData中,校验。
LinkedList转化为ArrayList时可使用此方法。

六、N个方法 公共API

1.⭐根据下标获取元素 get(index)

public E get(int index) {rangeCheck(index);return elementData(index);}
检查下标是否正确,当下标大于等于容量大小,抛出“下标越界异常”
private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}
当下标为负数时,上一步校验不到,数组根据下标获取元素时由数组抛出“下标越界异常”

2.在指定位置塞入元素 set(index, element)

public E set(int index, E element) {rangeCheck(index);E oldValue = elementData(index);elementData[index] = element;return oldValue;}
1.检查传入的下标是否正确;
2.获取下标对应的元素;
3.替换该下标的元素;
4.返回旧元素;

3.⭐添加元素 add(E e)

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) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);}
private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity);}
private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);}
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;}
一、检查size+1后容量是否足够:1.如果此时数组是无参构造函数时返回的默认空数组,设置默认容量为10;2.校验元素个数是否大于数组容量,大于则触发扩容(1.5倍扩容);3.扩容:旧数组的长度(容量)加上自身右移1位后的数,右移相当于除以2,所以扩容后的新容量是旧容量的1.5倍;4.当旧数组长度为0时,经过上面的操作还是0,则设置为传入的数;5.当新数组长度大于size最大值时,将数组长度设置为int的最大值(感觉前面减的8,就是为这里准备的);6.扩容后将旧数组里的元素,复制到新数组Arrays.copyOf()7.Arrays.copyOf(),其实也是使用了 System.arraycopy()二、检查完容量后,在size++下标位置放入新元素
三、返回成功。

流程图:

在这里插入图片描述

4.在指定位置添加元素 add(int index, E element)

public void add(int index, E element) {rangeCheckForAdd(index);ensureCapacityInternal(size + 1);  // Increments modCount!!System.arraycopy(elementData, index, elementData, index + 1,size - index);elementData[index] = element;size++;}
1.校验传入下标:大于size或小于0,抛出“下标越界异常”;
2.检查容量是否足够,不够就扩容;
3.将指定位置及后面的元素都往后移,空出指定位置:将指定下标后面的元素复制到从这个下标+1开始的位置。下标位置的元素往后移,空出位置。
4.在指定位置放入新元素;
5.size++。

5.移除指定位置的元素 remove(int index)

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 workreturn oldValue;}
1.校验转入的下标是否正确;
2.获取下标对应的元素;
3.移除指定位置的元素:截取该下标的前后位置,copy到数组中。将指定下标+1开始的元素复制到从这个下标开始的位置,下标位置的元素往前移,覆盖下标位置的元素。
4.数组最后一个位置元素设置为null;
5.返回被删除的元素。

移除元素时,并不会改变 ArrayList的容量,只会自动扩容,不会自动减少容量,如果要减少容量,需要手动调用 trimToSize(),将容量修剪为与当前size大小相等的容量。

6.移除数组中第一次出现的指定元素 remove(Object o)

public boolean remove(Object o) {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;}
1.判空,传入的元素为空,返回false
2.遍历数组元素,找到与传入元素相等的
3.匹配到了,执行快速删除,返回true  (类似于删除指定下标位置的元素)
4.匹配不到,数组不变,返回false

7.清除数据 clear()

public void clear() {modCount++;// clear to let GC do its workfor (int i = 0; i < size; i++)elementData[i] = null;size = 0;}
1.循环遍历,将所有位置的元素都设置为 null
2.size修改为0;
此时数组容量不变,只是都存了空

8.修剪容量 trimToSize()

public void trimToSize() {modCount++;if (size < elementData.length) {elementData = (size == 0)? EMPTY_ELEMENTDATA: Arrays.copyOf(elementData, size);}}
将容量修剪为与当前size大小相等的容量。
ArrayList只会自动扩容,不会自动缩容,需要手动调用修剪容量。

9.获取元素数量 size()

public int size() {return size;}

10.判断列表是否为空 isEmpty()

public boolean isEmpty() {return size == 0;}

11.判断列表中是否包含某元素 contains(Object o)

public boolean contains(Object o) {return indexOf(o) >= 0;}

12.查询指定元素在列表中出现的第一个索引 indexOf(Object o)

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;}
1.顺序循环遍历数组,对比指定元素;
2.找到相等的元素,返回该元素的下标;
3.找不到,返回 -1;

13.查询指定元素在列表中出现的最后一个索引 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;}return -1;}
1.倒序遍历数组,对比指定元素;
2.找到相等的元素,返回该元素的下标;
3.找不到,返回 -1;

14.复制列表 clone()

public Object clone() {try {ArrayList<?> v = (ArrayList<?>) super.clone();v.elementData = Arrays.copyOf(elementData, size);v.modCount = 0;return v;} catch (CloneNotSupportedException e) {// this shouldn't happen, since we are Cloneablethrow new InternalError(e);}}
浅拷贝,只复制数组元素对象的引用。

15.列表转化为数组 toArray()

public Object[] toArray() {return Arrays.copyOf(elementData, size);}
将 elementData 复制到一个新数组并返回。

16.列表复制到指定数组 toArray(T[] a)

public <T> T[] toArray(T[] a) {if (a.length < size)// Make a new array of a's runtime type, but my contents:return (T[]) Arrays.copyOf(elementData, size, a.getClass());System.arraycopy(elementData, 0, a, 0, size);if (a.length > size)a[size] = null;return a;}

17.将指定集合里的所有元素添加到列表的末尾 addAll(Collection<? extends E> c)

public boolean addAll(Collection<? extends E> c) {Object[] a = c.toArray();int numNew = a.length;ensureCapacityInternal(size + numNew);  // Increments modCountSystem.arraycopy(a, 0, elementData, size, numNew);size += numNew;return numNew != 0;}
1.将传入的集合转为数组
2.检查容量是否足够,不够则扩容
3.将上面转化的数组复制到原数组,从末尾开始
4.修改size
5.传入的集合没有元素,返回false,有元素,返回true

18.将指定集合里的所有元素添加到列表的指定位置 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;ensureCapacityInternal(size + numNew);  // Increments modCountint numMoved = size - index;if (numMoved > 0)System.arraycopy(elementData, index, elementData, index + numNew,numMoved);System.arraycopy(a, 0, elementData, index, numNew);size += numNew;return numNew != 0;}

19.从此列表中删除包含在指定集合中的所有元素 removeAll(Collection<?> c)

public boolean removeAll(Collection<?> c) {Objects.requireNonNull(c);return batchRemove(c, false);}private boolean batchRemove(Collection<?> c, boolean complement) {final Object[] elementData = this.elementData;int r = 0, w = 0;boolean modified = false;try {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.if (r != size) {System.arraycopy(elementData, r,elementData, w,size - r);w += size - r;}if (w != size) {// clear to let GC do its workfor (int i = w; i < size; i++)elementData[i] = null;modCount += size - w;size = w;modified = true;}}return modified;}
1.循环遍历数组,调用Collection集合的contains方法判断集合中是否包含该元素,不包含则放入该元素
2.将后面没有移动的元素设置为null

20.从此列表中删除不包含在指定集合中的所有元素 retainAll(Collection<?> c)

public boolean retainAll(Collection<?> c) {Objects.requireNonNull(c);return batchRemove(c, true);}
与 removeAll(Collection<?> c) 相反

21.⭐分割列表 subList(int fromIndex, int toIndex)

public List<E> subList(int fromIndex, int toIndex) {subListRangeCheck(fromIndex, toIndex, size);return new SubList(this, 0, fromIndex, toIndex);}static void subListRangeCheck(int fromIndex, int toIndex, int size) {if (fromIndex < 0)throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);if (toIndex > size)throw new IndexOutOfBoundsException("toIndex = " + toIndex);if (fromIndex > toIndex)throw new IllegalArgumentException("fromIndex(" + fromIndex +") > toIndex(" + toIndex + ")");}
截取下标从 fromIndex 到 toIndex 的元素,返回一个新的List

这篇关于【抽丝剥茧】解析ArrayList源码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/737919

相关文章

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

Spring MVC使用视图解析的问题解读

《SpringMVC使用视图解析的问题解读》:本文主要介绍SpringMVC使用视图解析的问题解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC使用视图解析1. 会使用视图解析的情况2. 不会使用视图解析的情况总结Spring MVC使用视图

利用Python和C++解析gltf文件的示例详解

《利用Python和C++解析gltf文件的示例详解》gltf,全称是GLTransmissionFormat,是一种开放的3D文件格式,Python和C++是两个非常强大的工具,下面我们就来看看如何... 目录什么是gltf文件选择语言的原因安装必要的库解析gltf文件的步骤1. 读取gltf文件2. 提