Java集合——List接口学习总结

news2024/11/25 12:22:45

一、ArrayList实现类

1. 常用方法

	增加:add(int index, E element)
    删除:remove(int index)  remove(Object o)
    修改:set(int index, E element)
    查看:get(int index)
    判断:
	常用遍历方式:
		//List集合 遍历:
        //方式1:普通for循环:
        System.out.println("---------------------");
        for(int i = 0;i<list.size();i++){
            System.out.println(list.get(i));
        }
        //方式2:增强for循环:
        System.out.println("---------------------");
        for(Object obj:list){
            System.out.println(obj);
        }
        //方式3:迭代器:
        System.out.println("---------------------");
        Iterator it = list.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }

2. JDK1.8(jdk1.8.0_331)源码下(简要)

重要成员变量和构造方法

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
	//重要成员变量
	private static final int DEFAULT_CAPACITY = 10; //数组默认初始容量
	private static final Object[] EMPTY_ELEMENTDATA = {}; //实例对象的默认数组,是个空数组
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //与EMPTY_ELEMENTDATA类似,主要用于区分在首次添加元素时判断如何进行扩容
	transient Object[] elementData; //ArrayList中实际存储元素的Object数组
	private int size; //计算ArrayList数组的元素数量,不是elementData.length
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //要分配的数组的最大大小,也就是数组极限长度
	protected transient int modCount = 0;//给迭代器用的,在调用add和remove方法时修改数值
	
	
	//构造方法
	//空参构造,例如List list = new ArrayList<>();
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //初始化数组为空数组
    }
	//根据传入的initialCapacity,判断初始化数组长度为多少,例如List list1 = new ArrayList<>(100);
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity]; // >0时初始化
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA; //等于0时初始化为空数组
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+ //小于0时,抛出异常,代码健壮性考虑
                                               initialCapacity);
        }
    }
	//传入集合Collection下面的实现类,? extends E这个表达式涉及到泛型知识
	/*
		List中 <? extends xxx> 定义了List集合的泛型上限,就是定义了一个天花板,输入的类只能从天花板往下找当前类或子类
		List中<? super xxx> 定义了泛型的下限,就是定义了一个地板,输入的类只能从地板开始往上找当前类或者父类
		例如:
		List<Object> a = new ArrayList<>();
        List<Person> b = new ArrayList<>();
        List<Student> c = new ArrayList<>();
		List<? extends Person> list1 = null;
		List<? super Person> list2 = null;
		
		list1 = a;//编译报错,因为list1定义的泛型最大父类(天花板)就是Person,Object类是所有类的父类,超过了定义的天花板Person类
        list1 = b;//编译正常
        list1 = c;//编译正常
		
		list2 = a;//编译正常
        list2 = b;//编译正常
        list2 = c;//编译报错,因为list2定义的泛型最小父类(地板)就是Person,Student类是Person类子类,是低于定义的地板Person类
		List<? extends Person>:就相当于:List<? extends Person>是List<Person>的父类,也是List<Person的子类>的父类
		List<? super Person>是List<Person>的父类,也是List<Person的父类>的父类
	*/
    public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray(); //转成Obj数组,赋值给a
        if ((size = a.length) != 0) { //判断数组长度是否为0
            if (c.getClass() == ArrayList.class) { //判断c的类名是不是ArrayList
				//是的话就直接赋值给数组elementData
                elementData = a; 
            } else {
				//否则就拷贝复制新数组给elementData
                elementData = Arrays.copyOf(a, size, Object[].class); 
            }
        } else {
            //数组长度不为0,则实例初始化为空数组
            elementData = EMPTY_ELEMENTDATA;
        }
    }
}

常用方法:add

	//常用方法add
	public boolean add(E e) {
        ensureCapacityInternal(size + 1);//这一步是计算和判断数组容量
        elementData[size++] = e; //将传入元素放在数组下标为size的位置,然后size+1,下标是从0开始
        return true;
    }
	private void ensureCapacityInternal(int minCapacity) { 
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
	private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
			//如果数组是个空数组的话,返回DEFAULT_CAPACITY和minCapacity的最大值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
	private void ensureExplicitCapacity(int minCapacity) {
        modCount++; //迭代器用的计数值,可忽略
        if (minCapacity - elementData.length > 0) //当 size+1 也就是当前数组元素数量+1,大于当前elementData数组长度时,就需要针对elementData数组扩容
            grow(minCapacity);
    }
	private void grow(int minCapacity) {
        int oldCapacity = elementData.length; //原先老数组长度
        int newCapacity = oldCapacity + (oldCapacity >> 1); //新数组长度计算,大概是1.5倍,旧数组长度+(旧数组长度/2)
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity; //newCapacity < minCapacity时,newCapacity = minCapacity
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity); //newCapacity超出数组定义的极限长度时的处理
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
	private static int hugeCapacity(int minCapacity) { //超出极限长度时
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError(); //抛出异常
		//判断是否大于MAX_ARRAY_SIZE,返回是话Integer.MAX_VALUE,否则MAX_ARRAY_SIZE。
		//MAX_ARRAY_SIZE之所以是Integer.MAX_VALUE - 8,多出来的8 int(整数) 就等于32 bytes(字节)是防止内存溢出用的,更深入的话涉及JVM和计算机底层了。
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; 
    }

样例理解:插入第一个元素时
在这里插入图片描述
插入超过10个元素时:
在这里插入图片描述
常用方法:remove,这个可以自己看源码,实际原理就是先找到元素e,然后通过System.arraycopy方法把元素e后面所有元素往前移动一位,再把元素末尾置位null

    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 work
        return oldValue;
    }
	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;
	 }
	 private void fastRemove(int index) {
        modCount++;
        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 work
    }

总结:

  • ArrayList底层是使用Object数组实现的,当创建对象时,若使用无参构造,则初始容量为0,此时第一次调用add方法添加就需要扩容为10,元素数量超过数组长度时会进行数组扩容,如需再次扩容为1.5倍。
  • 优点:底层是数组,查询效率高,数据可重复
  • 缺点:添加或者删除时效率低

3.在JDK1.7和1.8中的区别

在这里插入图片描述

4.ListIterator迭代器

在这里插入图片描述
出错原因:就是迭代器it和list同时对集合进行操作导致。
出错详解:调用add方法时,ArrayList成员变量里操作次数modCount发生了改变,例如上述代码调用到的add(“ee”)时ArrayList中的成员变量modCount=5(执行了5次add)。list.iterator()返回的Iterator iterator实际是ArrayList源码中内部类Itr实现了接口Iterator。在ArrayList源码中内部类Itr有个成员变量expectedModCount(预期的操作次数)记录,在调用完add(“kk”)后modCount=6,但是expectedModCount还是5,然后继续while走到if时调用next()方法时发现两个变量不一致抛出了并发修改异常。
源码图解:
在这里插入图片描述

解决方案:事情让一个“人”做 --》引入新的迭代器:ListIterator。迭代和添加操作都是靠ListIterator来完成的,ListIterator底层实际就通过在同一个数组上,用指针的概念在调整位置。
在这里插入图片描述
实际上看源码就知道做了什么事情,就是ArrayList源码实现接口ListIterator自己实现了add方法,把expectedModCount和modCount同步一下。同时内部类Itr实现了remove方法,跟内部类ListItr的add方法是一个道理。
在这里插入图片描述
在这里插入图片描述

5.面试题:iterator(),Iterator,Iterable关系

在这里插入图片描述
1.iterator()是Iterable接口中定义的方法,用于返回一个迭代器(Iterator)对象。

2.Iterator接口是Java集合框架中用于遍历集合元素的标准接口,它定义了一些方法,如hasNext()、next()、remove()等,用于遍历集合中的元素并对其进行操作。

3.Iterable接口是Java集合框架中用于表示“可迭代”的对象的标准接口,它定义了一个iterator()方法,用于返回一个迭代器(Iterator)对象,从而可以遍历集合中的元素。

hashNext()、next()方法源码简化
在这里插入图片描述

二、Vector实现类

1. 与ArrayList区别与联系

区别:

  • Vector底层扩容长度为原数组2倍,线程安全,效率低
  • ArrayList底层扩容长度为原数组1.5倍,线程不安全,效率高

联系:

  • 底层都是数组,具有数组的优缺点(查询快,增删慢)
  • 都继承了AbstractList类并实现List接口

在这里插入图片描述
add方法源码简化

public class Vector<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
	protected Object[] elementData; //数组
	protected int elementCount;//元素个数
	protected int capacityIncrement;//扩容数量
  private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  public Vector() {
    //创建时默认数组长度为10,ArrayList创建出来的时候是空数组,第一次调用add才会创建长度10的数组
      this(10); 
  }
  public Vector(int initialCapacity) {
        this(initialCapacity, 0);
  }
  public Vector(int initialCapacity, int capacityIncrement) {
        super();
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
  }
  public Vector(Collection<? extends E> c) {
        Object[] a = c.toArray();
        elementCount = a.length;
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, elementCount, Object[].class);
        }
  }
  //为什么Vector是线程安全的,因为方法上都有synchronized关键字进行同步
  public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
  }
  private void ensureCapacityHelper(int minCapacity) {
      // overflow-conscious code
      if (minCapacity - elementData.length > 0)
          grow(minCapacity);
  }
  private void grow(int minCapacity) {
      // overflow-conscious code
      int oldCapacity = elementData.length;
      //这里就是两倍扩容的代码了,在capacityIncrement一直为0的时候,就是oldCapacity+oldCapacity
      int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                       capacityIncrement : oldCapacity);
      if (newCapacity - minCapacity < 0)
          newCapacity = minCapacity;
      if (newCapacity - MAX_ARRAY_SIZE > 0)
          newCapacity = hugeCapacity(minCapacity);
      elementData = Arrays.copyOf(elementData, newCapacity);
  }
  
}
  

三、 LinkedList实现类

1. 简要原理图

在这里插入图片描述

2. 源码分析(简化版)

JDK1.7和JDK1.8的LinkedList的源码是一致的

public class LinkedList<E>{//E是一个泛型,具体的类型要在实例化的时候才会最终确定
    transient int size = 0;//集合中元素的数量
        //Node的内部类
   private static class Node<E> {
        E item;//当前元素
        Node<E> next;//指向下一个元素地址
        Node<E> prev;//上一个元素地址
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
    transient Node<E> first;//链表的首节点
    ransient Node<E> last;//链表的尾节点
        //空构造器:
    public LinkedList() {}
        //添加元素操作:
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
   void linkLast(E e) {//添加的元素e
        final Node<E> l = last;//将链表中的last节点给l 如果是第一个元素的话 l为null
                //将元素封装为一个Node具体的对象:
        final Node<E> newNode = new Node<>(l, e, null);
                //将链表的last节点指向新的创建的对象:
        last = newNode;
                
        if (l == null)//如果添加的是第一个节点
            first = newNode;//将链表的first节点指向为新节点
        else//如果添加的不是第一个节点 
            l.next = newNode;//将l的下一个指向为新的节点
        size++;//集合中元素数量加1操作
        modCount++;
    }
        //获取集合中元素数量
    public int size() {
        return size;
    }
        //通过索引得到元素:
    public E get(int index) {
        checkElementIndex(index);//健壮性考虑
        return node(index).item;
    }
        
    Node<E> node(int index) {
        //如果index在链表的前半段,那么从前往后找
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {//如果index在链表的后半段,那么从后往前找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
}

四、其他问题

1.ArrayList源码和Vector源码中的都会有一个成员变量private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,这个数组容量上限MAX_ARRAY_SIZE为什么要-8?

其实源码中成员变量上面就有英文解释,简要回答就是保证跨平台,有些虚拟机数组会有会有保留字,出于安全性和健壮性考虑所以做了长度限制。可参考以下博客说明。
https://blog.csdn.net/qq_44734036/article/details/118982215

为什么是-8可以参考以下博客说明。
https://blog.csdn.net/fisherish/article/details/117134717

实际上ArrayList能不能直接到达Integer.MAX_VALUE这个,我认为是可以的,ArrayList源码扩容函数里面实际是允许的,只是一开始new ArrayList<>(Integer.MAX_VALUE);这个会报错,但是如果进行扩容操作的话能达到。这个只是从源码上进行的推论,没有实际能验证过。
以下为源码说明:
在这里插入图片描述

五.Collection部分整体结构图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/417660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023MathorCup数模C题思路数据代码论文【全网最全分享】

文章目录赛题思路赛题详情参赛建议&#xff08;个人见解&#xff09;选择队友及任务分配问题&#xff08;重要程度&#xff1a;5星&#xff09;2023MathorCup数模C题思路数据论文代码【最新】赛题思路 (赛题出来以后第一时间在CSDN分享) 最新进度在文章最下方卡片&#xff0c;…

Atlassian后Server时代 | Server版vs.数据中心版,二者的区别在哪里?

2024年2月&#xff0c;也就是一年不到&#xff0c;Atlassian将终止对Server产品及插件的所有支持。 此公告发布后&#xff0c;许多用户需要了解怎样的前进方向才是最适合企业的。为此&#xff0c;Atlassian提供了本地部署的数据中心&#xff08;Data Center&#xff09;版以及云…

浅聊MVCC,希望能对你有帮助

浅聊MVCC&#xff0c;希望能对你有帮助&#x1f3cd; 前言 多版本并发控制是数据库管理系统中的一项重要技术&#xff0c;它可以提高数据库的并发性能和可靠性&#xff0c;支持高并发的读写操作&#xff0c;提高数据的安全性&#xff0c;具有重要的应用价值和意义。笔者写此文…

好用的5款国产低代码平台介绍

一、云程低代码平台 云程低代码平台是一款基于springboot、vue.js技术的企业级低代码开发平台&#xff0c;平台采用模型驱动、高低码融合、开放扩展等设计理念&#xff0c;基于业务建模、流程建模、表单建模、报表建模、大屏建模等可视化建模工具&#xff0c;通过拖拉拽零代码方…

深入理解Linux多线程

致前行的人&#xff1a; 昨日渐多&#xff0c;明日愈少&#xff0c;今日还在&#xff0c;不要为成功而努力&#xff0c;要为做一个有价值的人而努力。人生道路上充满了坎坷&#xff0c;谁也不可能一帆风顺。只有在最困难的时刻&#xff0c;才能体会到无助的含义。 目录 1.理解…

ESP32-LORA通信

文章目录好久没更新博客了&#xff0c;今天清明节&#xff0c;写个LORA通信。在此记念在天堂的外婆。祝她安好LORA通信简介一、模块二、使用步骤1.电脑通过USB串口模块联接LORA模块2.ESP32连接LORA通信进行收发通信3.电脑运行调试助手&#xff0c;ESP32运行代码。实现LORA通信测…

3.5 函数的极值与最大值和最小值

学习目标&#xff1a; 我要学习函数的极值、最大值和最小值&#xff0c;我会采取以下几个步骤&#xff1a; 理解基本概念&#xff1a;首先&#xff0c;我会理解函数的极值、最大值和最小值的概念。例如&#xff0c;我会学习函数在特定区间内的最高点和最低点&#xff0c;并且理…

ChatGPT的“N宗罪”?|AI百态(上篇)

序&#xff1a; AI诞生伊始&#xff0c;那是人人欣喜若狂的科技曙光&#xff0c;深埋于哲学、想象和虚构中的古老的梦&#xff0c;终于成真&#xff0c;一个个肉眼可见的智能机器人&#xff0c;在复刻、模仿和服务着他们的造物主——人类。 但科技树的点亮&#xff0c;总会遇到…

解决python中import导入自己的包呈现灰色 无效的问题

打开File–> Setting—> 打开 Console下的Python Console&#xff0c;把选项&#xff08;Add source roots to PYTHONPAT&#xff09;点击勾选上。 右键点击需要导入的工作空间文件夹&#xff0c;找到Mark Directory as 选择Source Root。 另外&#xff0c;Python中的…

自然语言处理(六): Deep Learning for NLP: Feedforward Networks

目录 1. Deep Learning 1.2 Feed-forward NN 1.3 Neuron 1.4 Matrix Vector Notation 矩阵向量表示法 1.5 Output Layer 1.6 Learning from Data 1.7 Regularisation 正则化 1.8 Dropout 2. Applications in NLP 2.1 Topic Classification 2.2 Topic Classification…

如何在 Linux 中使用 Chage 命令,修改Linux系统用户密码更改策略

Chage是一个用于修改Linux系统用户密码更改策略的命令行工具。在本文中&#xff0c;我们将介绍如何在Linux系统中使用Chage命令。 检查用户密码过期信息 使用Chage命令可以检查用户密码更改策略和过期信息。要检查特定用户的密码过期信息&#xff0c;可以使用以下命令&#x…

【区块链】走进web3的世界-gas费用

气体单位用于衡量在以太坊上执行交易所需的计算量。由于每笔交易都需要一些计算资源来执行&#xff0c;因此需要一笔费用&#xff0c;通常称为Gas fee或Transaction fee 。 汽油费以以太坊的本地货币——ether或ETH支付。汽油费的计算方式在伦敦升级前后略有不同。 注意&#…

进程间通信之共享内存

共享内存一. 什么是共享内存二. 共享内存有关函数1.获取key2.打开创建共享内存对象 - shmget3.映射空间地址 - shmat4.取消映射 - shmdt5.删除共享内存对象 - shmctl三. 实例四. 注意事项1.查看当前系统的共享内存2.当两个进程间ftok参数不一样时,shmid也不一样,共享内存不是同…

第04章_IDEA的安装与使用(下)

第04章_IDEA的安装与使用&#xff08;下&#xff09; 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 8. 快捷键的使用 8.1 常用快捷键 见《尚硅谷_宋红康_IntelliJ IDEA 常用快捷键一览表.md》…

【机器视觉1】光源介绍与选择

文章目录一、常见照明光源类型二、照明光源对比三、照明技术3.1 亮视野与暗视野3.2 低角度照明3.3 前向光直射照明3.4 前向光漫射照明3.5 背光照明-测量系统的最佳选择3.6 颜色与补色示例3.7 偏光技术应用四、镜头4.1 镜头的几个概念4.2 影响图像质量的关键因素4.3 成像尺寸4.4…

迁移Visual Studio2022到非系统盘

参考&#xff1a;VS2019/VS2022移动安装位置CSDN博客 已经安装VS Studio2022&#xff0c;默认在了C盘&#xff0c;下面是VS2022更换安装位置的方法&#xff1a;将安装好的文件剪切到其他盘&#xff0c;然后mklink链接。 第一步&#xff1a;将安装好的文件剪切到其他盘 以下为…

C++ 实现 matlab 的 buttord函数

文章目录1. matlab 的 buttord函数 的作用2. matlab 的 buttord函数 的使用方法3. C 实现代码4. 测试代码和结果4.1 定义滤波器的设计指标的结构体4.2 C 测试文件4.3 测试结果1. matlab 的 buttord函数 的作用 根据给定的巴特沃斯滤波器的指标计算滤波器的最低阶数和截止频率 …

【蓝桥杯嵌入式】蓝桥杯嵌入式第十四届省赛程序真题,真题分析与代码讲解

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都已更新完毕&#xff0c;欢迎大家前往订阅本专题&#x1f38f; &#x1f38f;【蓝桥杯嵌入式】蓝桥杯第十届省赛真题 &#x1f38f;【蓝桥杯嵌入式】蓝桥…

k8s ingress controller 使用

目录 一 安装Ingress controller 二 创建service deploy 三 创建ingress 四 测试 一 安装Ingress controller apiVersion: v1 kind: Namespace metadata:name: ingress-nginxlabels:app.kubernetes.io/name: ingress-nginxapp.kubernetes.io/instance: ingress-nginx--- # …

怎么把wav转换成mp3格式,5种方法值得收藏

怎么把wav转换成mp3格式&#xff1f;wav格式相信很多小伙伴们不是很熟悉&#xff0c;这种文件格式通常用于录音室等一些专业音乐项目上&#xff0c;那么wav格式和mp3格式有什么区别呢&#xff1f;wav全名Waveform Audio File Format&#xff0c;是微软公司开发的一种声音文件格…