java集合基础

Java的java.util包主要提供了以下三种类型的集合:

  • List:一种有序列表的集合,例如,按索引排列的StudentList
  • Set:一种保证没有重复元素的集合,例如,所有无重复名称的StudentSet
  • Map:一种通过键值(key-value)查找的映射表集合,例如,根据Studentname查找对应StudentMap

Java的集合类定义在java.util包中,支持泛型,主要提供了3种集合类,包括ListSetMap。Java集合使用统一的Iterator遍历,尽量不要使用遗留接口。

List

ArrayList在内部使用了数组来存储所有元素。例如,一个ArrayList拥有5个元素,实际数组大小为6(即有一个空位):

size=5
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │   │
└───┴───┴───┴───┴───┴───┘

当添加一个元素并指定索引到ArrayList时,ArrayList自动移动需要移动的元素:

size=5
┌───┬───┬───┬───┬───┬───┐
│ A │ B │   │ C │ D │ E │
└───┴───┴───┴───┴───┴───┘

然后,往内部指定索引的数组位置添加一个元素,然后把size1

size=6
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ F │ C │ D │ E │
└───┴───┴───┴───┴───┴───┘

继续添加元素,但是数组已满,没有空闲位置的时候,ArrayList先创建一个更大的新数组,然后把旧数组的所有元素复制到新数组,紧接着用新数组取代旧数组:

size=6
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ F │ C │ D │ E │   │   │   │   │   │   │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

现在,新数组就有了空位,可以继续添加一个元素到数组末尾,同时size1

size=7
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ F │ C │ D │ E │ G │   │   │   │   │   │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

可见,ArrayList把添加和删除的操作封装起来,让我们操作List类似于操作数组,却不用关心内部元素如何移动。

但是,实现List接口并非只能通过数组(即ArrayList的实现方式)来实现,另一种LinkedList通过“链表”也实现了List接口。在LinkedList中,它的内部每个元素都指向下一个元素:

        ┌───┬───┐   ┌───┬───┐   ┌───┬───┐   ┌───┬───┐
HEAD ──▶│ A │ ●─┼──▶│ B │ ●─┼──▶│ C │ ●─┼──▶│ D │   │└───┴───┘   └───┴───┘   └───┴───┘   └───┴───┘

我们考察List<E>接口,可以看到几个主要的接口方法:

  • 在末尾添加一个元素:boolean add(E e)
  • 在指定索引添加一个元素:boolean add(int index, E e)
  • 删除指定索引的元素:E remove(int index)
  • 删除某个元素:boolean remove(Object e)
  • 获取指定索引的元素:E get(int index)
  • 获取链表大小(包含元素的个数):int size()

创建List

List<String> list = new ArrayList<>();

ArrayList类继承自List接口 

  • () 是构造函数调用的语法,用于实例化 ArrayList 对象。
  • <> 中的空白部分是 钻石操作符,编译器会根据变量声明的类型自动推断出泛型类型,简化了代码的书写。

所以我们要始终坚持使用迭代器Iterator来访问ListIterator本身也是一个对象,但它是由List的实例调用iterator()方法的时候创建的。Iterator对象知道如何遍历一个List,并且不同的List类型,返回的Iterator对象实现也是不同的,但总是具有最高的访问效率。

Iterator对象有两个方法:boolean hasNext()判断是否有下一个元素,E next()返回下一个元素。因此,使用Iterator遍历List代码如下:

import java.util.Iterator;
import java.util.List;public class Main {public static void main(String[] args) {List<String> list = List.of("apple", "pear", "banana");for (Iterator<String> it = list.iterator(); it.hasNext(); ) {String s = it.next();System.out.println(s);}}
}
import java.util.List;public class Main {public static void main(String[] args) {List<String> list = List.of("apple", "pear", "banana");for (String s : list) {System.out.println(s);}}
}

上述代码就是我们编写遍历List的常见代码。

实际上,只要实现了Iterable接口的集合类都可以直接用for each循环来遍历,Java编译器本身并不知道如何遍历集合对象,但它会自动把for each循环变成Iterator的调用,原因就在于Iterable接口定义了一个Iterator<E> iterator()方法,强迫集合类必须返回一个Iterator实例。

List是接口  Array是一种数据类型

List和Array转换

List变为Array有三种方法,第一种是调用toArray()方法直接返回一个Object[]数组:

第二种方式是给toArray(T[])传入一个类型相同的ArrayList内部自动把元素复制到传入的Array中:

import java.util.List;public class Main {public static void main(String[] args) {List<Integer> list = List.of(12, 34, 56);Integer[] array = list.toArray(new Integer[3]);for (Integer n : array) {System.out.println(n);}}
}

注意到这个toArray(T[])方法的泛型参数<T>并不是List接口定义的泛型参数<E>,所以,我们实际上可以传入其他类型的数组,例如我们传入Number类型的数组,返回的仍然是Number类型:

如果传入的数组不够大,那么List内部会创建一个新的刚好够大的数组,填充后返回;如果传入的数组比List元素还要多,那么填充完元素后,剩下的数组元素一律填充null

实际上,最常用的是传入一个“恰好”大小的数组:

Integer[] array = list.toArray(new Integer[list.size()]);

最后一种更简洁的写法是通过List接口定义的T[] toArray(IntFunction<T[]> generator)方法:

Integer[] array = list.toArray(Integer[]::new);

这种函数式写法我们会在后续讲到。

Arrat变成List

通过List.of(T...)方法最简单:

Integer[] array = { 1, 2, 3 };
List<Integer> list = List.of(array);

List是按索引顺序访问的长度可变的有序表,优先使用ArrayList而不是LinkedList

可以直接使用for each遍历List

List可以和Array相互转换。

List中查找元素时,List的实现类通过元素的equals()方法比较两个元素是否相等,因此,放入的元素必须正确覆写equals()方法,Java标准库提供的StringInteger等已经覆写了equals()方法;

编写equals()方法可借助Objects.equals()判断。

如果不在List中查找元素,就不必覆写equals()方法。

Map<K, V>是一种键-值映射表,当我们调用put(K key, V value)方法时,就把keyvalue做了映射并放入Map。当我们调用V get(K key)时,就可以通过key获取到对应的value。如果key不存在,则返回null。和List类似,Map也是一个接口,最常用的实现类是HashMap

重复放入key-value并不会有任何问题,但是一个key只能关联一个value。在上面的代码中,一开始我们把key对象"apple"映射到Integer对象123,然后再次调用put()方法把"apple"映射到789,这时,原来关联的value对象123就被“冲掉”了。实际上,put()方法的签名是V put(K key, V value),如果放入的key已经存在,put()方法会返回被删除的旧的value否则,返回null

遍历Map

Map来说,要遍历key可以使用for each循环遍历Map实例的keySet()方法返回的Set集合,它包含不重复的key的集合:

import java.util.HashMap;
import java.util.Map;public class Main {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("apple", 123);map.put("pear", 456);map.put("banana", 789);for (String key : map.keySet()) {Integer value = map.get(key);System.out.println(key + " = " + value);}}
}

同时遍历keyvalue可以使用for each循环遍历Map对象的entrySet()集合,它包含每一个key-value映射:

import java.util.HashMap;
import java.util.Map;public class Main {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();map.put("apple", 123);map.put("pear", 456);map.put("banana", 789);for (Map.Entry<String, Integer> entry : map.entrySet()) {String key = entry.getKey();Integer value = entry.getValue();System.out.println(key + " = " + value);}}
}

如果Map的key是enum类型,推荐使用EnumMap,既保证速度,也不浪费空间。

使用EnumMap的时候,根据面向抽象编程的原则,应持有Map接口。

 

使用TreeMap时,放入的Key必须实现Comparable接口。StringInteger这些类已经实现了Comparable接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。

如果作为Key的class没有实现Comparable接口,那么,必须在创建TreeMap时同时指定一个自定义排序算法:

import java.util.*;public class Main {public static void main(String[] args) {Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {public int compare(Person p1, Person p2) {return p1.name.compareTo(p2.name);}});map.put(new Person("Tom"), 1);map.put(new Person("Bob"), 2);map.put(new Person("Lily"), 3);for (Person key : map.keySet()) {System.out.println(key);}// {Person: Bob}, {Person: Lily}, {Person: Tom}System.out.println(map.get(new Person("Bob"))); // 2}
}class Person {public String name;Person(String name) {this.name = name;}public String toString() {return "{Person: " + name + "}";}
}

使用TreeMap时,对Key的比较需要正确实现相等、大于和小于逻辑! 

SortedMap在遍历时严格按照Key的顺序遍历,最常用的实现类是TreeMap

作为SortedMap的Key必须实现Comparable接口,或者传入Comparator

要严格按照compare()规范实现比较逻辑,否则,TreeMap将不能正常工作

队列(Queue)是一种经常使用的集合。Queue实际上是实现了一个先进先出(FIFO:First In First Out)的有序表。它和List的区别在于,List可以在任意位置添加和删除元素,而Queue只有两个操作:

  • 把元素添加到队列末尾;
  • 从队列头部取出元素。

在Java的标准库中,队列接口Queue定义了以下几个方法:

throw Exception返回false或null
添加元素到队尾add(E e)boolean offer(E e)
取队首元素并删除E remove()E poll()
取队首元素但不删除E element()E peek()

PriorityQueueQueue的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue调用remove()poll()方法,返回的总是优先级最高的元素。

要使用PriorityQueue,我们就必须给每个元素定义“优先级”。我们以实际代码为例,先看看PriorityQueue的行为:

import java.util.PriorityQueue;
import java.util.Queue;public class Main {public static void main(String[] args) {Queue<String> q = new PriorityQueue<>();// 添加3个元素到队列:q.offer("apple");q.offer("pear");q.offer("banana");System.out.println(q.poll()); // appleSystem.out.println(q.poll()); // bananaSystem.out.println(q.poll()); // pearSystem.out.println(q.poll()); // null,因为队列为空}
}

我们放入的顺序是"apple""pear""banana",但是取出的顺序却是"apple""banana""pear",这是因为从字符串的排序看,"apple"排在最前面,"pear"排在最后面。

因此,放入PriorityQueue的元素,必须实现Comparable接口PriorityQueue会根据元素的排序顺序决定出队的优先级。

如果我们要放入的元素并没有实现Comparable接口怎么办?PriorityQueue允许我们提供一个Comparator对象来判断两个元素的顺序。我们以银行排队业务为例,实现一个PriorityQueue

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;public class Main {public static void main(String[] args) {Queue<User> q = new PriorityQueue<>(new UserComparator());// 添加3个元素到队列:q.offer(new User("Bob", "A1"));q.offer(new User("Alice", "A2"));q.offer(new User("Boss", "V1"));System.out.println(q.poll()); // Boss/V1System.out.println(q.poll()); // Bob/A1System.out.println(q.poll()); // Alice/A2System.out.println(q.poll()); // null,因为队列为空}
}class UserComparator implements Comparator<User> {public int compare(User u1, User u2) {if (u1.number.charAt(0) == u2.number.charAt(0)) {// 如果两人的号都是A开头或者都是V开头,比较号的大小:return u1.number.compareTo(u2.number);}if (u1.number.charAt(0) == 'V') {// u1的号码是V开头,优先级高:return -1;} else {return 1;}}
}class User {public final String name;public final String number;public User(String name, String number) {this.name = name;this.number = number;}public String toString() {return name + "/" + number;}
}

实现PriorityQueue的关键在于提供的UserComparator对象,它负责比较两个元素的大小(较小的在前)。UserComparator总是把V开头的号码优先返回,只有在开头相同的时候,才比较号码大

栈(Stack)是一种后进先出(LIFO:Last In First Out)的数据结构。

什么是LIFO呢?我们先回顾一下Queue的特点FIFO:

           ────────────────────────(\(\       (\(\    (\(\    (\(\       (\(\(='.') ──▶ (='.')  (='.')  (='.') ──▶ (='.')
O(_")")    O(_")") O(_")") O(_")")    O(_")")────────────────────────

所谓FIFO,是最先进队列的元素一定最早出队列,而LIFO是最后进Stack的元素一定最早出Stack。如何做到这一点呢?只需要把队列的一端封死:

            ───────────────────────────────┐(\(\        (\(\    (\(\    (\(\    (\(\ │(='.') ◀──▶ (='.')  (='.')  (='.')  (='.')│
O(_")")     O(_")") O(_")") O(_")") O(_")")│───────────────────────────────┘

因此,Stack是这样一种数据结构:只能不断地往Stack中压入(push)元素,最后进去的必须最早弹出(pop)来:

Stack只有入栈和出栈的操作:

  • 把元素压栈:push(E)
  • 把栈顶的元素“弹出”:pop()
  • 取栈顶元素但不弹出:peek()

在Java中,我们用Deque可以实现Stack的功能:

  • 把元素压栈:push(E)/addFirst(E)
  • 把栈顶的元素“弹出”:pop()/removeFirst()
  • 取栈顶元素但不弹出:peek()/peekFirst()

为什么Java的集合类没有单独的Stack接口呢?因为有个遗留类名字就叫Stack,出于兼容性考虑,所以没办法创建Stack接口,只能用Deque接口来“模拟”一个Stack了。

当我们把Deque作为Stack使用时,注意只调用push()/pop()/peek()方法,不要调用addFirst()/removeFirst()/peekFirst()方法,这样代码更加清

Stack的作用

Stack在计算机中使用非常广泛,JVM在处理Java方法调用的时候就会通过栈这种数据结构维护方法调用的层次。

  • main 方法调用

    • JVM 执行到 main 方法时,为 main 方法创建一个栈帧,并将其压入调用栈。
    • main 方法内部调用了 sum 方法。
  • sum 方法调用

    • sum 方法被调用时,JVM 为 sum 方法创建一个新的栈帧。
    • sum 方法的参数 ab(分别为 10 和 20)会存储在栈帧的局部变量表中。
  • 方法执行

    • sum 方法执行 a + b 的加法操作,计算结果(30)存放在操作数栈中。
  • sum 方法返回

    • sum 方法执行完毕后,栈帧被弹出,返回值(30)会传递给 main 方法,main 方法继续执行 System.out.println(result)
  • main 方法结束

    • 最后,main 方法执行完毕,它的栈帧也会被弹出,程序结束。

Collections是JDK提供的工具类,同样位于java.util包中。它提供了一系列静态方法,能更方便地操作各种集合。

 注意

Collections结尾多了一个s,不是Collection!

我们一般看方法名和参数就可以确认Collections提供的该方法的功能。例如,对于以下静态方法:

public static boolean addAll(Collection<? super T> c, T... elements) { ... }

addAll()方法可以给一个Collection类型的集合添加若干元素。因为方法签名是Collection,所以我们可以传入ListSet等各种集合类型。

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

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

相关文章

用前端html如何实现2024烟花效果

用HTML、CSS和JavaScript编写的网页&#xff0c;主要用于展示“2024新年快乐&#xff01;”的文字形式烟花效果。下面是对代码主要部分的分析&#xff1a; HTML结构 包含三个<canvas>元素&#xff0c;用于绘制动画。引入百度统计的脚本。 CSS样式 设置body的背景为黑…

批量合并多个Excel到一个文件

工作中&#xff0c;我们经常需要将多个Excel的数据进行合并&#xff0c;很多插件都可以做这个功能。但是今天我们将介绍一个完全免费的独立软件【非插件】&#xff0c;来更加方便的实现这个功能。 准备Excel 这里我们准备了两张待合并的Excel文件 的卢易表 打开的卢易表软件…

GLM4模型详解 - 智谱AI开源大模型全面解析

&#x1f4da; 2024年6月5日,智谱AI在开发者大会上正式开源GLM-4-9B系列大模型。本文将全面解析GLM4的技术特点、部署方案和应用场景。 GLM-4-9B 模型具备了更强大的推理性能、更长的上下文处理能力、多语言、多模态和 All Tools 等突出能力。 “All Tools” 一、模型概述 1.…

DataEase 开源 BI 工具 v2.10.3 LTS 发布

DataEase 开源 BI 工具 v2.10.3 LTS 发布 2024 年 12 月 9 日&#xff0c;开源 BI 工具 DataEase 正式发布 v2.10.3 LTS 版本。 此次更新带来了多方面的功能变化&#xff1a; 数据源&#xff1a;API 数据源与 Excel 数据源可设定字段类型与长度&#xff0c;优化数据接入的精准…

ionic V6 安装ios所需

npm install capacitor/ios添加ios平台 ruby要求3.0以上 rvm use ruby-3.1.0 --default npx cap add ios打开xcode看看创建的项目 npx cap open ios没有capacitor指定的位置, 估计之前pod(cocoapods)安装搞得Ruby环境很乱了......cocoapods整的我麻了... App/App/capacitor…

51单片机--- 串口控制仿真

51单片机--- 串口控制仿真 实验目标:51单片机接收串口数据,根据数据点亮LED。 实验步骤: 在Proteus里画出原理图 在Keil里用C语言编写程序 在Proteus中导入HEX文件,启动仿真 实验协议: 波特率115200 数据位:8位,停止位:1位,校验位:无。 命令格式: 一条命令为…

ssm-day04 mybatis

mybatis是一个持久层框架&#xff0c;针对的是JDBC的优化 简化数据库操作&#xff0c;能进行单表、多表操作&#xff0c;在这个框架下&#xff0c;需要我们自己写SQL语句 Mapper接口和MapperXML文件就相当于Dao和Dao层的实现 通常将xml文件放在resources包下 &#xff0c;放在…

观测云亮相 Doris Summit,展示流式聚合创新技术

01 技术亮点&#xff1a;重新定义流式聚合的效率与体验 在演讲中&#xff0c;熊豹详细介绍了 GuanceDB 如何支撑观测云的全量业务场景&#xff0c;并分享了 SelectDB 在处理复杂查询中的应用与挑战。从动态表结构管理到函数下推优化&#xff0c;观测云通过灵活的架构设计有效解…

qt中tr的使用

在 Qt 中使用 tr 函数对字符串进行翻译时&#xff0c;通常会通过 Qt Linguist 工具来处理翻译。以下是一个基本的步骤说明&#xff0c;展示如何将 QPushButton *btnnew QPushButton(tr("Hello World"),this); 翻译成其他语言&#xff0c;比如中文&#xff1a; 1.创建…

因特网的发展三个阶段

因特网的发展大致分为哪几个阶段&#xff0c;这几个阶段的主要特点 第一个阶段——单个网络到互联网 1969年美国创建第一个分组交换的单个网络ARPANET&#xff08;单个的分组交换网&#xff09;所有要连接在ARPANET上的主机都直接与就近的节点交换机相连&#xff1b; 20世纪…

基于X410的LabVIEW FPGA跳频通信系统开发

跳频通信系统是一种高效的无线通信技术&#xff0c;通过在多个频率间快速切换&#xff0c;提高抗干扰能力和信号安全性。以NI USRP X410为硬件平台&#xff0c;利用LabVIEW FPGA实现一个实时跳频通信系统&#xff0c;涵盖信号生成、触发控制、接收检测及实时数据处理。 系统架构…

校园交友app/校园资源共享小程序/校园圈子集合二手物品交易论坛、交友等综合型生活服务社交论坛

多客校园社交圈子系统搭建 校园交友多功能系统源码: 1、更改学校为独立的模块。整体UI改为绿色&#xff0c;青春色&#xff0c;更贴近校园风格。2、圈子归纳到学校去进行运营。每个学校可建立多个圈子。和其他学校圈子互不干扰。3、增加用户绑定学校&#xff0c;以后进入将默认…

医院跌倒检测识别 使用YOLO,COCO ,VOC格式对4806张原始图片进行标注,可识别病人跌倒,病人的危险行为,病床等场景,预测准确率可达96.7%

医院跌倒检测识别 使用YOLO,COCO ,VOC格式对4806张原始图片进行标注&#xff0c;可识别病人跌倒&#xff0c;病人的危险行为&#xff0c;病床等场景&#xff0c;预测准确率可达96.7&#xff05; 数据集分割 4806总图像数 训练组70&#xff05; 3364图片 有效集20&#…

SQL进阶技巧:如何根据工业制程参数计算良品率?

目录 0 问题描述 1 数据准备 2 问题分析 步骤1&#xff1a;确定每个生产批次的制程参数是否在合格范围内 步骤2&#xff1a;基于中间结果一计算临时良品数量 步骤3&#xff1a;计算良品率&#xff08;最终结果&#xff09; 3 小结 问题拓展&#xff1a;如果制程参数是动…

RK3568平台(内存篇)内存管理架构

一.内存管理架构 内核管理内存的基本单位page页: 物理内存被分割成相同大小的组,内核用页来表示这个组,页是内核管理内存的基本单位,尽管处理器的最小可寻址单位通常是字, 但是, 内存管理单元MMU通常以页为单位进行处理. 因此,从虚拟内存的上来看,页就是最小单位. 页帧…

常见漏洞—ssti

我之前在做了几道题之后写了一篇只包含python环境的ssti的总结&#xff0c;后来刷portswigger lab的时候才发觉自己先入为主了&#xff0c;所以决定重新写一篇。 因本人技术浅薄&#xff0c;只对见过的几个模板做简单介绍&#xff0c;如果想看有深度的文章&#xff0c;可以直接…

WEB语义化的新探索:浅析LLMs.txt

【引】有人迷恋使用大模型生成各种有趣的内容&#xff0c; 有人沉醉于大模型相关技术的探索&#xff0c;没有对错&#xff0c;只在于你的乐趣所在。 一项名为 llms.txt 的新提案标志了一些非同寻常的东西的出现: 一个Web网站不仅为人类读者服务&#xff0c;而且为人工智能提供服…

MacOs使用Wine 安装UaExpert与UaExpert的使用

要在 macOS 上使用 Wine 安装和运行 UaExpert&#xff0c;可以按照以下步骤操作&#xff1a; 安装 Wine 在 macOS 上&#xff0c;你可以通过 Homebrew 来安装 Wine。如果你还没有安装 Homebrew&#xff0c;可以先安装 Homebrew&#xff0c;然后使用它来安装 Wine。 bash /bin…

scp命令

scp&#xff08;Secure Copy Protocol&#xff09;是一种用于在不同主机之间安全传输文件的命令。使用 scp 命令&#xff0c;你可以将文件从本地计算机复制到远程计算机&#xff0c;或者从远程计算机复制到本地计算机。 以下是 scp 命令的基本语法和一些示例&#xff1a; 基本…

YOLOv10改进,YOLOv10利用DLKAttention融合DCNv3、DCNv4形成全新的可变形大核注意力,并二次创新C2f结构,全网首发

理论介绍 完成本篇需要参考以下三篇文章,并已添加到YOLOv10代码中 YOLOv10改进,YOLOv10添加DCNv3可变性卷积与C2f结构融合(无需编译)YOLOv10改进,YOLOv10添加DCNv4可变性卷积(windows系统成功编译),全网最详细教程YOLOv10改进,YOLOv10添加DLKA-Attention可变形大核注意力…