源码分析之ArrayList容器

分手后的思念是犯贱 2022-11-13 10:22 309阅读 0赞

源码分析之ArrayList容器

    • 1、ArrayList容器概述
    • 2、ArrayList类主要属性
    • 3、ArrayList类构造器
    • 4、查找相关方法
    • 5、插入相关方法
    • 6、删除相关方法
    • 7、其它方法
    • 8、总结
    • 9、往期佳文
      • 9.1、面试系列
      • 9.2、技术系列
      • 9.3、源码系列

1、ArrayList容器概述

  1. Java中的ArrayList类声明如下:
  2. public class ArrayList<E> extends AbstractList<E>
  3. implements List<E>, RandomAccess, Cloneable, java.io.Serializable

在这里插入图片描述
Java中的ArrayList容器是一个可动态调整大小的数组,每当我们往容器中插入元素都不必考虑容器是否装的下,其内部可以自动扩展大小。ArrayList容器的实现原理是封装了一个数组,每当插入元素就判断当前数组是否还有空位置存放,如果没有空位置,则新建一个更长的数组,并且把之前的元素复制到新数组中。

2、ArrayList类主要属性

  1. ArrayList类中的主要属性如下:
  2. /** * 默认数组初始化的大小 */
  3. private static final int DEFAULT_CAPACITY = 10;
  4. /** * 空数组实例(当初始化容量=0时) */
  5. private static final Object[] EMPTY_ELEMENTDATA = { };
  6. /** * 调用默认构造器时,初始化的空数组实例 */
  7. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = { };
  8. /** * ArrayList底层存放元素使用的数组 */
  9. transient Object[] elementData;
  10. /** * 数组中存放的元素个数(注意与数组长度的区分) */
  11. private int size;

3、ArrayList类构造器

  1. ArrayList类中共有3个构造器:
  2. /** * 指定数组的初始化长度 */
  3. public ArrayList(int initialCapacity) {
  4. // 主要是初始化elementData数组
  5. if (initialCapacity > 0) {
  6. this.elementData = new Object[initialCapacity];
  7. } else if (initialCapacity == 0) {
  8. // EMPTY_ELEMENTDATA默认的空数组实现(前面介绍了这个静态属性)
  9. this.elementData = EMPTY_ELEMENTDATA;
  10. } else {
  11. throw new IllegalArgumentException("Illegal Capacity: "+
  12. initialCapacity);
  13. }
  14. }
  15. /** * 默认构造器 */
  16. public ArrayList() {
  17. // 表示数组未初始化(DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组空实现,前面介绍过这个静态属性)
  18. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  19. }
  20. /** * 复制构造器 */
  21. public ArrayList(Collection<? extends E> c) {
  22. // 调用容器c中的toArray方法,获取数组形式的数据
  23. elementData = c.toArray();
  24. if ((size = elementData.length) != 0) {
  25. // c.toArray可能返回的不是Object[].class,所以进行类型转换
  26. if (elementData.getClass() != Object[].class)
  27. // 调用Arrays.copyOf方法,copy出一个Object[].class形式的副本
  28. elementData = Arrays.copyOf(elementData, size, Object[].class);
  29. } else {
  30. // 如果容器c为空,则初始化elementData为空数组示例
  31. this.elementData = EMPTY_ELEMENTDATA;
  32. }
  33. }

4、查找相关方法

  1. 1get方法
  2. /** * 通过index获取elementData中的元素(此方法未使用任何权限修饰符,所以默认是default,在当前包下可使用) */
  3. @SuppressWarnings("unchecked")
  4. E elementData(int index) {
  5. return (E) elementData[index];
  6. }
  7. /** * 通过index获取elementData中的元素(public修饰,对外展示接口) */
  8. public E get(int index) {
  9. // 查找前先判断index是否超过了size(数组中元素的个数)
  10. rangeCheck(index);
  11. return elementData(index);
  12. }
  13. 2contains方法
  14. /** * 判断Object是否存在于容器中 */
  15. public boolean contains(Object o) {
  16. // 如果能查找到该元素的下标,则说明是存在的
  17. return indexOf(o) >= 0;
  18. }
  19. 3indexOflastIndexOf方法
  20. /** * 查找Object在容器数组中的第一个下标(未查找到返回-1) */
  21. public int indexOf(Object o) {
  22. // 如果o为null,则o需要==判断,否则调用equals方法判断是否相等
  23. if (o == null) {
  24. for (int i = 0; i < size; i++)
  25. if (elementData[i]==null)
  26. return i;
  27. } else {
  28. for (int i = 0; i < size; i++)
  29. if (o.equals(elementData[i]))
  30. return i;
  31. }
  32. return -1;
  33. }
  34. /** * 查找Object在容器数组中的最后一个下标(未查找到返回-1) */
  35. public int lastIndexOf(Object o) {
  36. // 与indexOf的区别就是从后往前查找,找的的就是最后一个下标
  37. if (o == null) {
  38. for (int i = size-1; i >= 0; i--)
  39. if (elementData[i]==null)
  40. return i;
  41. } else {
  42. for (int i = size-1; i >= 0; i--)
  43. if (o.equals(elementData[i]))
  44. return i;
  45. }
  46. return -1;
  47. }

5、插入相关方法

  1. 1add方法
  2. /** * 往容器尾端增加一个元素 */
  3. public boolean add(E e) {
  4. // 增加元素前需要判断是否有空位置(没有就需要扩容
  5. ensureCapacityInternal(size + 1);
  6. // 直接放入elementData数组中的下一个空位置
  7. elementData[size++] = e;
  8. return true;
  9. }
  10. /** * 往容器指定下标插入一个元素 */
  11. public void add(int index, E element) {
  12. // 插入前需要判断插入的下标是否在[0, size]中,也就是不能插入到非连续的其它位置
  13. rangeCheckForAdd(index);
  14. // 当然还需要判断容器是否有空间供插入
  15. ensureCapacityInternal(size + 1);
  16. // arraycopy的五个参数分别是,数组src,起始下标,数组des,起始下标,复制的个数
  17. // 此时我们需要把elementData[index]这个位置空出来,也就是[index, size)区间的元素全部往后移动一个位置,index移动到index+1,index+1移动到index+2
  18. System.arraycopy(elementData, index, elementData, index + 1, size - index);
  19. // 现在就可以放入插入的元素了
  20. elementData[index] = element;
  21. // 容器中的元素数量自增1
  22. size++;
  23. }
  24. 2set方法
  25. /** * 此方法是修改指定下标的元素值 */
  26. public E set(int index, E element) {
  27. // 如果这个下标超出[0, size)都算错误!(注意与[0, elementData.length)的区别)
  28. // 因为这是修改方法,如果这个index对应没有值,则不能修改
  29. rangeCheck(index);
  30. // 修改前记录旧值,在替换心值
  31. E oldValue = elementData(index);
  32. elementData[index] = element;
  33. return oldValue;
  34. }

6、删除相关方法

  1. 1remove方法
  2. /** * 移除容器中指定下标的元素 */
  3. public E remove(int index) {
  4. // 检查下标是否在[0, size)区间中
  5. rangeCheck(index);
  6. // 父类AbstractList中的一个变量,用于记录结构性调整(插入、删除、修改等)的次数
  7. modCount++;
  8. E oldValue = elementData(index);
  9. // [index + 1, size)区间的中的元素全部前一个位置
  10. int numMoved = size - index - 1;
  11. // arraycopy的五个参数分别是,数组src,起始下标,数组des,起始下标,复制的个数
  12. if (numMoved > 0)
  13. System.arraycopy(elementData, index+1, elementData, index, numMoved);
  14. // 将size - 1的元素移除
  15. elementData[--size] = null;
  16. return oldValue;
  17. }
  18. /** * 移除容器中的o对象(第一个) */
  19. public boolean remove(Object o) {
  20. // 遍历一遍容器,把值为对象o的元素全部移除
  21. if (o == null) {
  22. for (int index = 0; index < size; index++)
  23. if (elementData[index] == null) {
  24. // 此方法移除时不判断index的合法性,直接移除(因为传入的下标就是合法的)
  25. fastRemove(index);
  26. return true;
  27. }
  28. } else {
  29. for (int index = 0; index < size; index++)
  30. if (o.equals(elementData[index])) {
  31. fastRemove(index);
  32. return true;
  33. }
  34. }
  35. return false;
  36. }
  37. /* * 移除index下标对应的元素(不进行index判断,默认index是合法的) */
  38. private void fastRemove(int index) {
  39. // 与public E remove(int index)方法是一样的,只是没了index判断
  40. modCount++;
  41. int numMoved = size - index - 1;
  42. if (numMoved > 0)
  43. System.arraycopy(elementData, index+1, elementData, index, numMoved);
  44. elementData[--size] = null;
  45. }

7、其它方法

  1. 1trimToSize方法
  2. /** * 此方法的作用是将elementData数组中的空余位置剔除(有点像瘦身) */
  3. public void trimToSize() {
  4. // 次方法也属于结构性调整
  5. modCount++;
  6. if (size < elementData.length) {
  7. // 如果elementData数组存在元素,则调用Arrays.copyOf方法,复制到一个长度为size的数组中
  8. elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
  9. }
  10. }
  11. 2grow扩容方法
  12. /** * 增加elementData数组的长度 */
  13. private void grow(int minCapacity) {
  14. // 新长度为原长度的1.5倍
  15. int oldCapacity = elementData.length;
  16. int newCapacity = oldCapacity + (oldCapacity >> 1);
  17. // 如果新长度 比 最小的长度小,则修改为最小的长度
  18. if (newCapacity - minCapacity < 0)
  19. newCapacity = minCapacity;
  20. // 如果新长度比MAX_ARRAY_SIZE还大,修改为默认的最大值
  21. if (newCapacity - MAX_ARRAY_SIZE > 0)
  22. newCapacity = hugeCapacity(minCapacity);
  23. // 调用Arrays.copyOf方法,复制到一个长度为newCapacity的数组中
  24. elementData = Arrays.copyOf(elementData, newCapacity);
  25. }

8、总结

  1. 经过上面的分析不难看出,ArrayList容器的底层实现特别简单,就是封装了一个数组,每当插入元素就判断一下是否容的下,容不下就扩容。ArrayList容器支持尾端插入、根据下标插入(随机插入);支持按下标查找(随机查找)、按值查找。

9、往期佳文

9.1、面试系列

1、吊打面试官之一面自我介绍
2、吊打面试官之一面项目介绍
3、吊打面试官之一面系统架构设计
4、吊打面试官之一面你负责哪一块
5、吊打面试官之一面试官提问

······持续更新中······


9.2、技术系列

1、吊打面试官之分布式会话
2、吊打面试官之分布式锁
3、吊打面试官之乐观锁
4、吊打面试官之幂等性问题
5、吊打面试关之分布式事务
6、吊打面试官之项目线上问题排查

······持续更新中······

9.3、源码系列

1、源码分析之SpringBoot启动流程原理
2、源码分析之SpringBoot自动装配原理

······持续更新中······


转载:Java容器之ArrayList源码分析

发表评论

表情:
评论列表 (有 0 条评论,309人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java容器ArrayList分析

    一、什么是容器 容器,用来存储一类相同的数据。数组是一种容器,它是一种线性结构,在查找方面,效率非常高,但是数组的长度需要我们在创建的时候,就设定好。这一性质,让它有些时