由浅入深理解java集合(三)——集合 List

news2024/11/29 9:43:13

一、List集合
List集合判断元素相等的标准
List判断两个对象相等只要通过equals()方法比较返回true即可(关于equals()方法的详解可以参考第二篇文章中的内容)。
下面以用代码具体展示。
创建一个Book类,并重写equals()方法,如果两个Book对象的name属性相同,则认为两个对象相等。

public class Book {
    public String name;

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Book other = (Book) obj;
        if (this.name == other.name) {
            return true;
        } 
        return false;
    }
}

向List集合中加入book1对象,然后调用remove(Object o)方法,从集合中删除指定对象,这个时候指定的对象是book2。

public static void main(String[] args){
        Book book1 = new Book();
        book1.name = "Effective Java";
        Book book2 = new Book();
        book2.name = "Effective Java";
        List<Book> list = new ArrayList<Book>();
        list.add(book1);
        list.remove(book2);
        System.out.println(list.size());
    }

输出结果:
0

可见把book1对象从集合中删除了,这表明List集合判断两个对象相等只要通过equals()方法比较返回true即可。
与Set不同,List还额外提供了一个listIterator()方法,该方法返回一个ListIterator对象。下面具体介绍下ListIterator。

ListIterator
ListIterator接口在Iterator接口基础上增加了如下方法:

**boolean hasPrevious(): **如果以逆向遍历列表。如果迭代器有上一个元素,则返回 true。
Object previous():返回迭代器的前一个元素。
void add(Object o):将指定的元素插入列表(可选操作)。

与Iterator相比,ListIterator增加了前向迭代的功能,还可以通过add()方法向List集合中添加元素。

二、ArrayList
既然要介绍ArrayList,那么就顺带一起介绍Vector。因为二者的用法功能非常相似,可以一起了解比对。

ArrayList简介
ArrayList和Vector作为List类的两个典型实现,完全支持之前介绍的List接口的全部功能。
ArrayList和Vector类都是基于数组实现的List类,所以ArrayList和Vector类封装了一个动态的、允许再分配的Object[]数组。ArrayList或Vector对象使用initalCapacity参数来设置该数组的长度,当向ArrayList或Vector中添加元素超过了该数组的长度时,它们的initalCapacity会自动增加。下面我们通过阅读JDK 1.8 ArrayList源码来了解这些内容。

ArrayList的本质
当以List list = new ArrayList(3);方式创建ArrayList集合时,

//动态Object数组,用来保存加入到ArrayList的元素
Object[] elementData;

//ArrayList的构造函数,传入参数为数组大小
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
             //创建一个对应大小的数组对象
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //传入数字为0,将elementData 指定为一个静态类型的空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

当以List list = new ArrayList();方式创建ArrayList集合时,不指定集合的大小

/**
     *Constructs an empty list with an initial capacity of ten。意思是:构造一个空数组,默认的容量为10
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

在这里可以看出private static final int DEFAULT_CAPACITY = 10;默认容量确实为10。
当向数组中添加元素list.add(book1);时:
先调用add(E e)方法

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 数组的大小增加1
        elementData[size++] = e;
        return true;
    }

在该方法中,先调用了一个ensureCapacityInternal()方法,顾名思义:该方法用来确保数组中是否还有足够容量。
经过一系列方法(不必关心),最后有个判断:如果剩余容量足够存放这个数据,则进行下一步,如果不够,则需要执行一个重要的方法:

 private void grow(int minCapacity) {
        //......省略部分内容  主要是为了生成大小合适的newCapacity
       //下面这行就是进行了数组扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

由此,我们就清楚地明白了,ArrayList是一个动态扩展的数组,Vector也同样如此。
如果开始就知道ArrayList或Vector集合需要保存多少个元素,则可以在创建它们时就指定initalCapacity的大小,这样可以提高性能。
此外,ArrayList还提供了两个额外的方法来调整其容量大小:

void ensureCapacity(int minCapacity): 如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。
void trimToSize():将此 ArrayList 实例的容量调整为列表的当前大小。

ArrayList和Vector的区别
1.ArrayList是线程不安全的,Vector是线程安全的。
2.Vector的性能比ArrayList差。

Stack
Stack是Vector的子类,用户模拟“栈”这种数据结构,“栈”通常是指“后进先出”(LIFO)的容器。最后“push”进栈的元素,将被最先“pop”出栈。如下图所示:

在这里插入图片描述
Stack类里提供了如下几个方法:
在这里插入图片描述
Stack与Vector一样,是线程安全的,但是性能较差,尽量少用Stack类。如果要实现栈”这种数据结构,可以考虑使用LinkedList(下面就会介绍)

ArrayList的遍历方式
ArrayList支持3种遍历方式
(01) 第一种,通过迭代器遍历

Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}

(02) 第二种,随机访问,通过索引值去遍历
由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。

Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
    value = (Integer)list.get(i);        
}

(03) 第三种,for循环遍历

Integer value = null;
for (Integer integ:list) {
    value = integ;
}

遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低。具体可以测试下。

三、LinkedList
LinkedList简介
LinkedList类是List接口的实现类——这意味着它是一个List集合,可以根据索引来随机访问集合中的元素。除此之外,LinkedList还实现了Deque接口,可以被当作成双端队列来使用,因此既可以被当成“栈"来使用,也可以当成队列来使用。

LinkedList的实现机制与ArrayList完全不同。ArrayList内部是以数组的形式来保存集合中的元素的,因此随机访问集合元素时有较好的性能;而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入、删除元素时性能比较出色。

由于LinkedList双端队列的特性,所以新增了一些方法。

LinkedList方法

void addFirst(E e):将指定元素插入此列表的开头。
void addLast(E e): 将指定元素添加到此列表的结尾。
E getFirst(E e): 返回此列表的第一个元素。
E getLast(E e): 返回此列表的最后一个元素。
boolean offerFirst(E e): 在此列表的开头插入指定的元素。
boolean offerLast(E e): 在此列表末尾插入指定的元素。
E peekFirst(E e): 获取但不移除此列表的第一个元素;如果此列表为空,则返回 nullE peekLast(E e): 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 nullE pollFirst(E e): 获取并移除此列表的第一个元素;如果此列表为空,则返回 nullE pollLast(E e): 获取并移除此列表的最后一个元素;如果此列表为空,则返回 nullE removeFirst(E e): 移除并返回此列表的第一个元素。
boolean removeFirstOccurrence(Objcet o): 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。
E removeLast(E e): 移除并返回此列表的最后一个元素。
boolean removeLastOccurrence(Objcet o): 从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。

下面我们就以阅读源码的方式来了解LinkedList内部是怎样维护链表的。

LinkedList本质
LinkedList调用默认构造函数,创建一个链表。由于维护了一个表头,表尾的Node对象的变量。可以进行后续的添加元素到链表中的操作,以及其他删除,插入等操作。也因此实现了双向队列的功能,即可向表头加入元素,也可以向表尾加入元素

//成员变量:表头,表尾
 transient Node<E> first;
transient Node<E> last;
//默认构造函数,表示创建一个空链表
public LinkedList() {
    }

下面来了解Node类的具体情况

private static class Node<E> {
        //表示集合元素的值
        E item;
       //指向下个元素
        Node<E> next;
     //指向上个元素
        Node<E> prev;
...................................省略
    }

由此可以具体了解链表是如何串联起来并且每个节点包含了传入集合的元素。
下面以增加操作,具体了解LinkedList的工作原理。

public boolean add(E e) {
        linkLast(e);
        return true;
    }

调用linkLast(e);方法,默认向表尾节点加入新的元素

 void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

更新表尾节点,建立连接。其他操作类似,维护了整个链表。
下面具体来看,如何将“双向链表和索引值联系起来的”?

 public E get(int index) {
        checkElementIndex(index);//检查索引是否有效
        return node(index).item;
    }
调用了node(index)方法返回了一个Node对象,其中node(index)方法具体如下

Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

首先会比较“index”和“双向链表长度的1/2”;若前者小,则从链表头开始往后查找,直到index位置;否则,从链表末尾开始先前查找,直到index位置。这就是“双线链表和索引值联系起来”的方法。
到此我们便会明白,LinkedList在插入、删除元素时性能比较出色,随机访问集合元素时性能较差。

LinkedList遍历方式
LinkedList支持多种遍历方式。

1.通过迭代器遍历LinkedList
2通过快速随机访问遍历LinkedList
3.通过for循环遍历LinkedList
4.通过pollFirst()遍历LinkedList
5.通过pollLast()遍历LinkedList
6通过removeFirst()遍历LinkedList
7.通过removeLast()遍历LinkedList

实现都比较简单,就不贴代码了。
其中采用逐个遍历的方式,效率比较高。采用随机访问的方式去遍历LinkedList的方式效率最低。

LinkedList也是非线程安全的。

四、ArrayList与LinkedList性能对比
ArrayList 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。ArrayList应使用随机访问(即,通过索引序号访问)遍历集合元素。
LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。LinkedList应使用采用逐个遍历的方式遍历集合元素。
如果涉及到“动态数组”、“栈”、“队列”、“链表”等结构,应该考虑用List,具体的选择哪个List,根据下面的标准来取舍。
(01) 对于需要快速插入,删除元素,应该使用LinkedList。
(02) 对于需要快速随机访问元素,应该使用ArrayList。
(03) 对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类(如ArrayList)。对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用同步的类(如Vector)。

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

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

相关文章

ctf.show MiSC入门 图片篇 (信息附加)

目录 图片篇 信息附加 misc5 misc6 misc7 misc8 misc9 misc10 misc11 misc12 misc13 misc14 misc15 misc16 misc17 misc18 misc19 misc20 misc 21 misc22 misc23 misc41 图片篇 信息附加 misc5 打开后啥也没有 使用16进制编辑器打开&#xff0c;在最下面…

就业内推 | 上市公司招网工运维,有华为、思科、华三认证均可

01 软通动力 &#x1f537;招聘岗位&#xff1a;网络工程师 &#x1f537;职责描述&#xff1a; 1、负责大型数据中心网络运维及变更&#xff0c;包括架构优化、性能调优、服务上线 2、负责网络故障类问题定位及排查&#xff0c;对于复杂故障类问题能够定位、跟进和解决实施等…

数据分析案例-BI工程师招聘岗位信息可视化分析(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

25.在vue中使用axios

目录 1 项目中安装 axios 2 做测试用的后端 3 全局引入axios 4 定义全局根路径 1 项目中安装 axios 2 做测试用的后端 我们用python的flask做后端 简单来讲就是&#xff0c;发两个数字&#xff0c;如果是get就两个数相加&#xff0c;如果是post就两个数相乘 3 全局引…

Kali-linux使用Nessus

Nessus号称是世界上最流行的漏洞扫描程序&#xff0c;全世界有超过75000个组织在使用它。该工具提供完整的电脑漏洞扫描服务&#xff0c;并随时更新其漏洞数据库。Nessus不同于传统的漏洞扫描软件&#xff0c;Nessus可同时在本机或远端上遥控&#xff0c;进行系统的漏洞分析扫描…

【数据结构初阶】——第八节.优先级队列(小根堆的模拟实现)

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;Java初阶数据结构 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 目录 …

API接口三问

一、API数据接口可以给我们带来哪些便利 API数据接口可以给我们带来以下便利&#xff1a; 数据共享&#xff1a;API允许数据在不同的应用程序之间共享。这使得数据转移更容易&#xff0c;因为不需要手动复制和粘贴数据内容。 程序集成&#xff1a;API作为中间件&#xff0c;可…

20年+资深审稿人:什么情况下建议文章大小修、拒稿或接收?

文章进入外审后&#xff0c;作者最终可能会得到大小修、接收或拒稿的意见。那么&#xff0c;审稿人是怎么给出这些不同意见的呢&#xff1f;有哪些方面需要作者提前了解呢&#xff1f; Surgery 发布过一篇文章&#xff0c;里面调查了一些具有20年审稿经验、平均年龄69岁的编委会…

ThreadLocal八股文

目录 1. 为什么要⽤ ThreadLocal? 2. ThreadLocal 的原理是什么&#xff1f; 3. 为什么⽤ ThreadLocal 做 key&#xff1f; 4. Entry 的 key 为什么设计成弱引⽤&#xff1f; 5. ThreadLocal 真的会导致内存泄露&#xff1f; 6. 如何解决内存泄露问题&#xff1f; 7. T…

1M分辨率 中国各城市绿地数据的获取

城市绿地系统是城市总体规划的有机组成部分&#xff0c;反映了城市的自然属性。在人类选址建造城市之初&#xff0c;大多将城市选择在和山、川、江、湖相毗邻的地方&#xff0c;它给予城市的形态、功能布局及城市景观以很大影响。先有自然&#xff0c;后有城市&#xff0c;自然…

时间序列预测(三)基于Prophet+XGBoost的销售额预测

时间序列预测&#xff08;三&#xff09;基于ProphetXGBoost的销售额预测 前面我们介绍了如何使用Prophet和LSTM&#xff0c;不知道你们发现了没有&#xff0c;前者似乎太简单了&#xff0c;后者呢好像又很复杂。那有没有什么很好的方法能很好的中和下呢&#xff1f; 已知的有…

入门黑客(网络安全)需要准备什么?

之所以写这篇文章呢&#xff0c;是觉得大时代的发展&#xff0c;我们这个专业越来越受到重视了&#xff0c;所以&#xff0c;也想以自己的一些拙见&#xff0c;能帮到想入门网络安全的朋友 1.关于网上的培训 如果你想快速获得知识&#xff0c;培训无疑是最快最有效的捷径&…

ANR概述

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、ANR是什么&#xff1f;二、ANR超时阈值三、前后与台区别1.前台与后台服务的区…

核心案例 | 南京理工大学空地协同编队控制系统建设项目

项目名称&#xff1a;空地协同编队控制系统建设项目 场 地&#xff1a;室内/室外 关 键 词&#xff1a;自主导航与SLAM、集群协同决策、集群控制 南京理工大学核心案例(1) 01 项目背景 本项目通过集群四旋翼无人机、天地协同集群控制开发环境、无人机协同集群控制系统…

音频转换成mp3的方法

把音频转换为MP3格式是因为MP3格式可以更好地压缩音频文件&#xff0c;减小文件大小&#xff0c;便于存储和传输。此外&#xff0c;MP3格式已成为流行的音频格式之一&#xff0c;许多设备和软件都支持MP3格式&#xff0c;使得MP3格式成为了一种通用的音频格式。总的来说&#x…

log4Qt史上最详细介绍、编译和使用

文章目录 Log4Qt介绍下载log4qt源码测试例子&#xff08;源码使用&#xff09;将log4qt源码添加到工程测试代码日志配置文件&#xff1a;测试结果 总结log4qt更多请参考&#xff1a; 使用Log4Qt动态库一、创建Qt工程&#xff0c;命名为libLog4Qt二、在项目根目录下新建文件夹3r…

Simulink使能(Enable)、触发(Triggered)模块及其子系统的应用

Simulink中的使能&#xff08;Enable&#xff09;和触发&#xff08;Triggered&#xff09;模块及其子系统可以用于控制模型中的仿真运行时间和采样周期&#xff0c;从而提高模型的仿真效率和精度。 使用使能子系统 创建一个在控制信号为正值时执行的子系统。使用触发子系统 创…

如何系列 如何在Windows和Linux安装Nginx

文章目录 Windows一 下载Nginx二 启动Nginx三 验证 Linux一 安装依赖项二 下载Nginx源码包三 安装四 验证五 常用命令附录 Nginx是一款高性能的开源Web服务器和反向代理服务器&#xff0c;被广泛用于构建现代化的Web应用和提供静态内容。本篇博文将教你如何在Windows和Linux操作…

Vue 前端代码多地部署(打包后配置动态IP)

Vue 前端代码多地部署&#xff08;打包后配置动态IP&#xff09; 需求一、使用 config.json二、使用 config.js 需求 vue 代码打包之后&#xff0c;需要在多个地方部署。正常操作是&#xff1a;先改 ip 地址&#xff0c;再打包。这样每换一个地方部署&#xff0c;就需要重新打…

将自己写的nginx.conf运行到阿里云linux服务器上

首先 你要保证自己的nginx.conf没有问题 可以先在本地运行一下 然后来到nginx.conf文件的所在目录 利用 scp -r ./nginx.conf 用户名(如果之前没设置过就是 root)服务器公网地址:/etc/nginx/将文件传到服务器上去 这里需要注意 如果你的服务器之前没有装过nginx 是没有这个目…