• JAVA----List接口的三种实现类


    常见的List接口的实现类

    • ArrayList:数组实现,查询快,增删慢,轻量级;(线程不安全)
    • LinkedList:双向链表实现,增删快,查询慢 (线程不安全)
    • Vector:数组实现,重量级 (线程安全、使用少)

    ArrayList的使用和实现

    List list=new ArrayList();

    public class ArrayList

    • extends AbstractList 通过继承抽象类可以共享所有公共方法
    • implements List, 实现List接口
    • RandomAccess,  实现随机访问接口
    • Cloneable, 实现克隆接口
    • java.io.Serializable  实现序列化接口
    • transient Object[] elementData;  真是存放数据的数组
    • private int size; 当前集合中存储的元素个数

     构造器

    1. public ArrayList() {
    2. //针对存储数据的数组进行初始化操作
    3. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    4. //常量定义为DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};空数组
    5. //如果使用无参构造器,则不会直接创建数组,而是采用空数组,当第一次添加元素时才创建数组
    6. //使用无参构造器时ArrayList会构建一个空数组用于未来存放数据,这里是一种对内存消耗的优化处理
    7. }
    8. 带参构造器   List list=new ArrayList(18
    9.     18就是初始化容积,这里的初始化参数值必须为[0,int的最大值)
    10. public ArrayList(int initialCapacity) { //参数为初始化容积
    11. if (initialCapacity > 0) { 如果初始化容积大于0,则按照指定的容积创建数组
    12. this.elementData = new Object[initialCapacity];
    13. } else if (initialCapacity == 0) {
    14. this.elementData = EMPTY_ELEMENTDATA; 空数组,长度为0的数组
    15. } else { //初始化容积值小于0则报异常
    16. throw new IllegalArgumentException("Illegal Capacity: "+
    17. initialCapacity);
    18. }
    19. }

    add方法的定义

    1. protected transient int modCount = 0;
    2. public boolean add(E e) {
    3. modCount++; 用于统计当前集合对象的修改次数
    4. add(e, elementData, size); 参数1:新增元素;参数2:存储数据的数组;参数3:当前集合中存储的元素个数
    5. return true; 返回true表示添加成功
    6. }
    7. private void add(E e, Object[] elementData, int s) {
    8. //如果数组已经存满了,则首先进行扩容处理
    9. if (s == elementData.length) 判断当前集合中存储的元素个数是否和数组长度相等
    10. elementData = grow(); 如果相等则进行扩容处理
    11. elementData[s] = e; 在数组的指定位置存储元素
    12. size = s + 1; 集合中所存储的元素个数
    13. }
    14. private Object[] grow() { //扩容处理方法
    15. return grow(size + 1); //继续调用其它扩容方法,参数为 当前存储个元素个数+1---最小容积
    16. }
    17. private Object[] grow(int minCapacity) {
    18. //调用Arrays工具类中的方法进行数组的拷贝,同时调用newCapacity方法计算所需要的新容积值
    19. return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));
    20. }
    21. private static final int DEFAULT_CAPACITY = 10;
    22. private int newCapacity(int minCapacity) { 计算所需要的新容积值
    23. int oldCapacity = elementData.length; 计算原来数组的长度
    24. //计算新容积值
    25. int newCapacity = oldCapacity + (oldCapacity >> 1); 相当于是老容积增加50%,这就是扩容比
    26. //计算出的新容积值是否满足所需要的最小容积值
    27. if (newCapacity - minCapacity <= 0) {
    28. //如果存储数据的数组为初始化时的空数组,则计算默认初始化容积10和所需要最小容积值则最大值
    29. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
    30. return Math.max(DEFAULT_CAPACITY, minCapacity);
    31. if (minCapacity < 0) // OOM内存溢出
    32. throw new OutOfMemoryError();
    33. return minCapacity;
    34. }
    35. //如果计算出的新容积值大于所需要的最小容积值
    36. return (newCapacity - MAX_ARRAY_SIZE <= 0)
    37. ? newCapacity
    38. : hugeCapacity(minCapacity);
    39. //如果计算出的新容积值小于最大允许的容积值,则返回计算出的新容积
    40. (minCapacity > MAX_ARRAY_SIZE)
    41. ? Integer.MAX_VALUE
    42. : MAX_ARRAY_SIZE;
    43. //如果计算出的新容积值大于最大允许的容积值并且minCapacity大于MAX_ARRAY_SIZE,则返回最大整数值,否则返回最大允许的容积值
    44. }
    45. //数组长度的上限值
    46. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    数组的拷贝操纵定义在Arrays工具类中,copyof会新建一个指定长度newLength的数组,并且将原始数组中的所有元素拷贝到新数组中,同时返回新创建的数组

    参数1是原始数组,参数2为所需要的新长度

    1. public static T[] copyOf(T[] original, int newLength) {
    2. return (T[]) copyOf(original, newLength, original.getClass());
    3. }

    The maximum size of array to allocate (unless necessary).

    Some VMs reserve some header words in an array.

    Attempts to allocate larger arrays may result in

    OutOfMemoryError: Requested array size exceeds VM limit

    要分配的最大数组大小(除非必要)。一些虚拟机在数组中保留一些头字。尝试分配较大的阵列可能会导致:OutOfMemoryError请求的数组大小超过VM限制

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    由于在不同的平台上,受到平台的影响导致能够为数组分配的实际最大数值并非为Integer.MAX_VALUE(2,147,483,647),而是与这个值相接近的数值。因此,减8实际上是因为不想让你创建的数组在扩容时计算的新容量值等于或过于接近最大值不能被平台分配出来而报出OOM错误

    结论:

    • 如果直接初始ArrayList,则采用延迟初始化处理【初始化数组长度为0】的方式以节约内存,当添加数据时,才进行数组的初始化,默认初始化容积为10
    • 添加数据时当存储数据的数组长度不足时,数组会自动变长,变长的比例为原容积的50%

    删除元素

    1. public E remove(int index) {
    2. Objects.checkIndex(index, size); //检查所需要删除位置下标参数是否在合理的范围内[0,size-1],如果超出范围则报异常
    3. final Object[] es = elementData; 获取所存储的所有元素数组
    4. E oldValue = (E) es[index]; 获取指定位置上的元素
    5. fastRemove(es, index); 利用System.arrayCopy的方法使用覆盖的方式删除指定位置上的元素
    6. return oldValue; 返回删除掉的元素
    7. //没有缩容处理
    8. }
    1. private void fastRemove(Object[] es, int i) {
    2. modCount++; 修改次数+1
    3. final int newSize;
    4. if ((newSize = size - 1) > i) //size - 1就是可以删除元素的最大下标
    5. System.arraycopy(es, i + 1, es, i, newSize - i); 通过数组拷贝的方式覆盖掉指定位置的元素
    6. es[size = newSize] = null; //最后位置赋null值
    7. {1,2,3,4,5} 执行arrayCopy方法需要覆盖下标为2的元素{1,2,4,5,5}
    8. }

    结论:

    • 使用数组元素移动的方式实现元素的删除。注意:这里没有变小容积
    • 修改元素个数时会有modCount的修改--快速失败

     get方法的实现

    1. public E get(int index) {
    2. Objects.checkIndex(index, size);针对索引下标进行合法性验证
    3. return elementData(index);
    4. }
    1. E elementData(int index) {
    2. return (E) elementData[index];
    3. }

    迭代器的实现类中定义【内部类】

    1. private void checkForComodification() {
    2. if (modCount != expectedModCount) {
    3. throw new ConcurrentModificationException(); 并发修改异常
    4. }
    5. }

    结论:

    首先要求index应该在[0,size-1]的范围内,否则异常
              如果index正确则按照下标从数组中获取元素

    如果多个线程同时操作一个ArrayList,可以通过ConcurrentModificationException解决修改出现的线程安全问题

  • 相关阅读:
    Citrix通过Azure Active Directory实现Saml认证单点登录云桌面
    目标检测笔记(十五): 使用YOLOX完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)
    第五章 Android应用核心Intent
    还在拿flex进行布局吗?快来试试grid网格布局吧
    K8s稳居容器榜首,Docker冲顶技术热词,微服务应用热度不减,2021云原生开发者现状
    压力测试-Jmeter脚本录制方案
    产品经理是干什么的?产品经理的工作内容与职责
    【ACWING】【2】前缀和
    WebGL☀️Unity WebGL适配到各平台的教程
    07_03文件系统怎么玩的
  • 原文地址:https://blog.csdn.net/weixin_64771061/article/details/126549521