ArrayList学习总结

作者: 沐风之境

ArrayList学习总结

文章结构

ArrayList容器

ArrayList内部的核心数据结构为Object数组,通过add(T e), get(int index), set(T e), remove(int index)等增删改查方法来实现对Object数组的操作。自动扩容机制也能保证ArrayList容器在使用中能够自动的适应数据容量,方便应用程序编写者的使用

类结构

核心数据结构

Object数组:Object[]

父类

AbstractList

实现的接口

  • List:支持所有的List方法

  • RandomAccess:可以根据数组角标进行快速的元素定位

  • Cloneable:可以使用clone()方法进行元实例的克隆

  • Serializable:能够实现对象的序列化

    线程的安全性

ArrayList的方法属于非线程安全性,所以要避免多个线程去修改共享ArrayList的应用场景。那么有什么可以替代的可以在多线程场景下使用的List实现类呢?目前有两个类可以代替ArrayList的多线程场景下的使用。

  • Vector类
  • OnWriteArrayList类

Vector类是是JDK1.0版本就有用于解决有序容器的实现类,但是Vector类上用于解决线程安全方式是使用synchronized关键字修饰的Java同步锁。而同步的方法需要依靠JVM的协调,一个线程访问Vector代码相比于非同步的方法会话费较多的时间。在JDK1.5出来之后,在相同场景下会优先使用OnWriteArrayList类。因为OnWriteArrayList类对读写锁进行了优化,不锁定读操作,而对写操作时会在当前调用栈中拷贝一份原来的核心数组,在拷贝的内存上进行修改,修改结束后将原本的内存指针指向新的内存地址。由这个原理们可以知道OnWriteArrayList类在数据容量比较大的时候需要很多的时间进行数据的拷贝,所以在数据量较小的时候是属于OnWriteArrayList的应用场景。

ArrayList核心的算法

扩容算法

下面代码拷贝自JDK11的ArrayList类

// 扩容调用方法,可以看到直接调用扩容方法就是扩容当前的实际容量 size + 1
private Object[] grow() {
  return grow(size + 1);
}

// 目前需要的最小的容量
private Object[] grow(int minCapacity) {
    // 获取真实需要扩容的内部Object数组大小,并将原数组进行拷贝
    return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}

// 确定需要扩容的容量
private int newCapacity(int minCapacity) {
  // overflow-conscious code
  int oldCapacity = elementData.length;

  // 这是扩容核心算法的核心,新的容量是 原容器容量 + 原容器容量的 * 0.5 => 也就是原容量的1.5倍
  // oldCapacity >> 1 右移动相当于 oldCapacity * 2^-1 => oldCapacity * 0.5
  // 这也是优化点,它这里问什么不用乘法或除法,而是用了位运算。因为jdk作为底层基础库必须考虑运行性能,而位运算的速度远远比乘除运算快
  int newCapacity = oldCapacity + (oldCapacity >> 1);
  if (newCapacity - minCapacity <= 0) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
      return Math.max(DEFAULT_CAPACITY, minCapacity);
    if (minCapacity < 0) // overflow
      throw new OutOfMemoryError();
    return minCapacity;
  }
  return (newCapacity - MAX_ARRAY_SIZE <= 0)
    ? newCapacity
    : hugeCapacity(minCapacity);
}

// 最大容量
private static int hugeCapacity(int minCapacity) {
  if (minCapacity < 0) // overflow
    throw new OutOfMemoryError();
  return (minCapacity > MAX_ARRAY_SIZE)
    ? Integer.MAX_VALUE
    : MAX_ARRAY_SIZE;
}

扩容逻辑

们可以看到上面的核心算法,每次调用grow方法都会进行数组的复制,如果每次add元素,都调用grow方法话肯定是不现实的这将会浪费大量的运算资源。

那类内部是如何扩容的呢?

public boolean add(E e) {
  modCount++;
  add(e, elementData, size);
  return true;
}

private void add(E e, Object[] elementData, int s) {
  // 们可以看到当时间容量等于内部Object数组的长度时才会自信扩容操作
  if (s == elementData.length)
    elementData = grow();
  elementData[s] = e;
  size = s + 1;
}

从上面的例子中也可以看出JDK内部为了提高运行的效率,运用了批量申请内存的思想。这与池化思想根本目的也是相同的。就是减少运算资源的消耗。这种优秀的编程思想在JDK的内部也处处可见。

使用的经典案例

摘自《JavaGuide》ArrayList源码章节https://snailclimb.gitee.io/javaguide//docs/java/collection/ArrayList

package list;
import java.util.ArrayList;
import java.util.Iterator;

public class ArrayListDemo {

    public static void main(String[] srgs){
         ArrayList<Integer> arrayList = new ArrayList<Integer>();

         System.out.printf("Before add:arrayList.size() = %d\n",arrayList.size());

         arrayList.add(1);
         arrayList.add(3);
         arrayList.add(5);
         arrayList.add(7);
         arrayList.add(9);
         System.out.printf("After add:arrayList.size() = %d\n",arrayList.size());

         System.out.println("Printing elements of arrayList");
         // 三种遍历方式打印元素
         // 第一种:通过迭代器遍历
         System.out.print("通过迭代器遍历:");
         Iterator<Integer> it = arrayList.iterator();
         while(it.hasNext()){
             System.out.print(it.next() + " ");
         }
         System.out.println();

         // 第二种:通过索引值遍历
         System.out.print("通过索引值遍历:");
         for(int i = 0; i < arrayList.size(); i++){
             System.out.print(arrayList.get(i) + " ");
         }
         System.out.println();

         // 第三种:for循环遍历
         System.out.print("for循环遍历:");
         for(Integer number : arrayList){
             System.out.print(number + " ");
         }

         // toArray用法
         // 第一种方式(最常用)
         Integer[] integer = arrayList.toArray(new Integer[0]);

         // 第二种方式(容易理解)
         Integer[] integer1 = new Integer[arrayList.size()];
         arrayList.toArray(integer1);

         // 抛出异常,java不支持向下转型
         //Integer[] integer2 = new Integer[arrayList.size()];
         //integer2 = arrayList.toArray();
         System.out.println();

         // 在指定位置添加元素
         arrayList.add(2,2);
         // 删除指定位置上的元素
         arrayList.remove(2);    
         // 删除指定元素
         arrayList.remove((Object)3);
         // 判断arrayList是否包含5
         System.out.println("ArrayList contains 5 is: " + arrayList.contains(5));

         // 清空ArrayList
         arrayList.clear();
         // 判断ArrayList是否为空
         System.out.println("ArrayList is empty: " + arrayList.isEmpty());
    }
}

使用ArrayList的注意事项

  • 非线程安全,切勿在夸多线程环境下修改ArrayList实例。请使用OnWriteArrayList类来代替

    ArrayList的优化手段

  • 实例化对象的时候指定数组的容量

    在实例化ArrayList对象的时候,构造器参数可以指定初始化的容器容量。目的是一次性申请足够的容量,避免自动扩容的时候进行无意义的数组拷贝,减少运算资源的浪费

  • trimToSize(),减小内存占用

    由于扩容的机制扩容都是原容量的1.5倍,在大容量实例的ArrayList实例中内存占用会越来越大,但业务上可以预知不再增加新的元素的时候,可以使用trimToSize()方法使ArrayList内存占用最小化

    参考资料

  • JavaGuide

    原文创作:沐风之境

    原文链接:https://www.cnblogs.com/mufeng3421/p/12923561.html

更多推荐

更多
  • AWS自动化机器学习-十一、MLSDLC 的持续集成、部署和训练 技术要求,编纂持续集成阶段,管理持续部署阶段,管理持续训练,延伸,构建集成工件,构建测试工件,构建生产工件,自动化持续集成流程,回顾构建阶段,回顾测试阶段,审查部署和维护阶段,回顾应用用户体验,创建新的鲍鱼调查数据,回顾持续训练流程,清
    Apache CN

  • AWS自动化机器学习-六、使用 AWS 步骤函数自动化机器学习过程 技术要求,介绍 AWS 步骤功能,使用 Step 函数 Data Science SDK for CI/CD,建立 CI/CD 渠道资源,创建状态机,解决状态机的复杂性,更新开发环境,创建管道工件库,构建管道应用构件,部署 CI/CD
    Apache CN

  • AWS自动化机器学习-第三部分:优化以源代码为中心的自动化机器学习方法 本节将向您介绍整体 CI/CD 流程的局限性,以及如何将 ML 从业者的角色进一步整合到管道构建流程中。本节还将介绍这种角色集成如何简化自动化过程,并通过向您介绍 AWS Step 函数向您展示一种优化的方法。本节包括以下章节:
    Apache CN

  • AWS自动化机器学习-一、AWS 上的自动化机器学习入门 技术要求,洗钱流程概述,洗钱过程的复杂性,端到端 ML 流程示例,AWS 如何使 ML 开发和部署过程更容易自动化,介绍 ACME 渔业物流,ML 的情况,从数据中获得洞察力,建立正确的模型,训练模型,评估训练好的模型,探索可能的后续步
    Apache CN

  • AWS自动化机器学习-二、使用 SageMaker 自动驾驶器自动化机器学习模型开发 技术要求,介绍 AWS AI 和 ML 前景,SageMaker 自动驾驶器概述,利用 SageMaker 自动驾驶器克服自动化挑战,使用 SageMaker SDK 自动化 ML 实验,SageMaker Studio 入门,准备实验
    Apache CN

  • AWS自动化机器学习-四、机器学习的持续集成和持续交(CI/CD) 四、机器学习的持续集成和持续交CI/CD技术要求,介绍 CI/CD 方法,通过 CI/CD 实现 ML 自动化,在 AWS 上创建 CI/CD 管道,介绍 CI/CD 的 CI 部分,介绍 CI/CD 的 CD 部分,结束循环,采取以部
    Apache CN

  • AWS自动化机器学习-九、使用 Amazon Managed Workflows 为 Apache AirFlow 构建 ML 工作流 技术要求,开发以数据为中心的工作流程,创建合成鲍鱼调查数据,执行以数据为中心的工作流程,构建和单元测试数据 ETL 工件,构建气流 DAG,清理, 在前面的年龄计算器示例中,我们了解了如何通过 ML 从业者和开发人员团队之间的跨职能
    Apache CN

  • AWS自动化机器学习-七、使用 AWS 步骤函数构建 ML 工作流 技术要求,构建状态机工作流,执行集成测试,监控管道进度,设置服务权限,创建 ML 工作流程, 在本章中,我们将从第六章中的 [处继续,使用 AWS 步骤函数自动化机器学习过程。您将从那一章中回忆起,我们正在努力实现的主要目标是简化
    Apache CN

  • AWS自动化机器学习-八、使用 Apache Airflow 实现机器学习过程的自动化 技术要求,介绍阿帕奇气流,介绍亚马逊 MWAA,利用气流处理鲍鱼数据集,配置 MWAA 系统的先决条件,配置 MWAA 环境, 当建立一个 ML 模型时,有一个所有 ML 从业者都知道的基本原则;也就是说,最大似然模型只有在数据被训练时
    Apache CN

  • AWS自动化机器学习-五、自动化 ML 模型的持续部署 技术要求,部署 CI/CD 管道,构建 ML 模型工件,执行自动化 ML 模型部署,整理管道结构,创建 CDK 应用,部署管道应用,查看建模文件,审查申请文件,查看模型服务文件,查看容器构建文件,提交 ML 工件,清理, 在 [第 4
    Apache CN

  • 近期文章

    更多
    文章目录

      推荐作者

      更多