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.rhkb.cn/news/492920.html

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

相关文章

springboot中Controller内文件上传到本地以及阿里云

上传文件的基本操作 <form action"/upload" method"post" enctype"multipart/form-data"> <h1>登录</h1> 姓名&#xff1a;<input type"text" name"username" required><br> 年龄&#xf…

pycharm debug

Pycharm Debug功能详解 1.跳转到当前断点(断点后你为了查看逻辑可能去了其他文件或行&#xff0c;点这个就能回到当前断点的行) 2.step over&#xff08;F8快捷键&#xff09;&#xff1a;在当前层代码单步执行。例子中即左下的堆栈&#xff0c;当前是test1.py第7行的fun函数&a…

webpack处理图片资源

过去在Webpack4时&#xff0c;我们处理图片资源通过file-loader和url-loader进行处理。 现在Webpack5已经将两个Loader功能内置到Webpack里了&#xff0c;我们只需要简单配置即可处理图片资源。 1. 配置 //webpack.config.js const path require("path");module.…

PHY6239:具有高精确度AFE的无线MCU芯片,常用在智能穿戴上

PHY6239是 一个 具有高精确度AFE的无线MCU。它具有低功耗32位处理器&#xff0c;8KB保留SRAM, 32KB OTP, 64KB ROM&#xff0c;和一个超低功耗&#xff0c;高性能的蓝牙5.4/2.4G无线电。 模拟前端提供24位Σ-Δ ADC&#xff0c;蕞大ENOB 22.6位&#xff0c;具有针对精密传感器测…

在 C# 中加载图像而不锁定文件

如何在不锁定图像文件的情况下将图像加载到 C# 程序中。单击“正常加载”按钮时&#xff0c;程序将使用以下代码显示图像文件。 // Load the image normally. private void btnLoadNormally_Click(object sender, EventArgs e) {if (picSample.Image ! null) picSample.Image.…

excel 使用vlook up找出两列中不同的内容

当使用 VLOOKUP 函数时&#xff0c;您可以将其用于比较两列的内容。假设您要比较 A 列和 B 列的内容&#xff0c;并将结果显示在 C 列&#xff0c;您可以在 C1 单元格中输入以下公式&#xff1a; 这个公式将在 B 列中的每个单元格中查找是否存在于 A 列中。如果在 A 列中找不到…

【原生js案例】前端封装ajax请求及node连接 MySQL获取真实数据

上篇文章&#xff0c;我们封装了ajax方法来请求后端数据&#xff0c;这篇文章将介绍如何使用 Node.js 来连接 MySQL&#xff0c;并对数据库进行操作。 实现效果 代码实现 后端接口处理 const express require("express"); const connection require("../da…

vue预览和下载 pdf、ppt、word、excel文档,文件类型为链接或者base64格式或者文件流,

** 方法1&#xff1a;word、xls、ppt、pdf 这些文件&#xff0c; 如果预览的文件是链接可以直接打开&#xff0c;可用微软官方的预览地址 ** <iframe width"100%" :src"textVisibleURl " id"myFramePPT" style"border: none;backgroun…

跟沐神学读论文-论文阅读管理

摘要 近期有读论文的需求&#xff0c;就需要去了解一下论文到底要怎么读&#xff0c;同一个系列之间的论文如何作整理和归纳&#xff0c;之前也有了解过市面上有成熟的论文阅读工具&#xff0c;但是对于学生党来讲没什么性价比&#xff0c;在B站上看到沐神有讲解他的思路Typor…

day38-SSH安全登录

机器准备 什么是SSH SSH 或 Secure Shell 协议是一种远程管理协议&#xff0c;允许用户通过 Internet 访问、控制和修改其远程服务器。 SSH 服务是作为未加密 Telnet 的安全替代品而创建的&#xff0c;它使用加密技术来确保进出远程服务器的所有通信都以加密方式进行。 SS…

使用React构建一个掷骰子的小游戏

这是一个用 React 构建的小游戏应用&#xff0c;名为 Tenzies&#xff0c;目标是掷骰子&#xff0c;直到所有骰子的值相同。玩家可以“冻结”某些骰子&#xff0c;使它们在后续掷骰中保持不变。 1. App.jsx import Die from "../public/components/Die" import { us…

vue 文本域 展示的内容格式要和填写时保持一致

文本域 展示的内容格式要和填写时保持一致 <el-inputtype"textarea":rows"5"placeholder"请输入内容"v-model"formCredit.point"style"width:1010px;" > </el-input> 样式加个&#xff1a; white-space: pre-w…

[Linux] 信号保存与处理

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;青果大战linux 总有光环在陨落&#xff0c;总有新星在闪烁 信号的保存 下面的概…

【Axure高保真原型】伸缩表单

今天和大家分享伸缩表单的原型模板&#xff0c;效果包括在需要填写内容较多时&#xff0c;可以对填写内容进行分类&#xff0c;然后通过点击上下箭头&#xff0c;收起或展开对应的信息。这个模版里面包含了输入框、下拉列表、选择器、上次图片共多种种常用的元件&#xff0c;后…

InternVL简读

InternVL: Scaling up Vision Foundation Models and Aligning for Generic Visual-Linguistic Tasks 1. Introduction 需要解决的问题&#xff1a; existing VLLMs [5, 81, 131, 177, 187] commonly employ lightweight “glue” layers, such as QFormer [81] or linear pr…

从源码分析swift GCD_DispatchGroup

前言&#xff1a; 最近在写需求的时候用到了DispatchGroup&#xff0c;一直没有深入去学习&#xff0c;既然遇到了那么就总结下吧。。。。 基本介绍&#xff1a; 任务组&#xff08;DispatchGroup&#xff09; DispatchGroup 可以将多个任务组合在一起并且监听它们的完成状态。…

AFL-Fuzz 的使用

AFL-Fuzz 的使用 一、工具二、有源码测试三、无源码测试 一、工具 建议安装LLVM并使用afl-clang-fast或afl-clang-lto进行编译&#xff0c;这些工具提供了更现代和高效的插桩技术。您可以按照以下步骤安装LLVM和afl-clang-fast&#xff1a; sudo apt update sudo apt install…

ONES 功能上新|ONES Copilot、ONES Wiki 新功能一览

ONES Copilot 可基于工作项的标题、描述、属性信息&#xff0c;对工作项产生的动态和评论生成总结。 针对不同类型的工作项&#xff0c;总结输出的内容有对应的侧重点。 应用场景&#xff1a; 在一些流程步骤复杂、上下游参与成员角色丰富的场景中&#xff0c;工作项动态往往会…

EasyGBS国标GB28181平台P2P远程访问故障排查指南:客户端角度的排查思路

在现代视频监控系统中&#xff0c;P2P&#xff08;点对点&#xff09;技术因其便捷性和高效性而被广泛应用。然而&#xff0c;当用户在使用P2P远程访问时遇到设备不在线或无法访问的问题时&#xff0c;有效的排查方法显得尤为重要。本文将从客户端的角度出发&#xff0c;详细探…

Soul Android端稳定性背后的那些事

前言&#xff1a;移动应用的稳定性对于用户体验和产品商业价值都有着至关重要的作用。应用崩溃会导致关键业务中断、用户留存率下降、品牌口碑变差、生命周期价值下降等影响&#xff0c;甚至会导致用户流失。因此&#xff0c;稳定性是APP质量构建体系中最基本和最关键的一环。当…