常用类库-集合
一. 类集概述
1. 集合与数组的区别,缓存的管理,Collection 、List 、Set 接口的作用及相关的子类。
2. 类集设置的目的
普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。
但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构,所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。
类集中最大的几个操作接口:Collection(单值存储集合的最大接口,专门存储单个数据,例如数组,每个下标对应一个值)、
Map(双值存储的最大接口,例如一个钥匙和一个锁)、
Iterator(迭代器,对于所有集合进行迭代获取数据)
这三个接口为以后要使用的最重点的接口。
所有的类集操作的接口或类都在 java.util 包中。
二. 常见数据结构
1. 栈
栈:stack,又称堆栈, 栈(stack)是限定仅在表尾进行插入和删除操作的线性表。我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。栈又称为先进后出的线性表 。
特点:
(1)先进后出
(2)栈的入口、出口的都是栈的顶端位置。
2. 队列
队列:queue,简称队, 队列是一种特殊的线性表,是运算受到限制的一种线性表,只允许在表的一端进行插入,而在另一端进行删除元素的线性表。队尾(rear)是允许插入的一端。队头(front)是允许删除的一端。空队列是不含元素的空表。
特点:
(1)先进先出;
(2)队列的入口、出口各占一侧。
3. 数组
数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。
特点:
(1)查找元素快:通过索引,可以快速访问指定位置的元素。
(2)增删元素慢:指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。
指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。
4. 链表
链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表。
特点:
(1)查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素。
(2)增删元素快:只需要修改连接下个元素的地址即可。
5. 二叉树
二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。
特点:查找元素快。
三. 链表和二叉树
1. 什么是链表?
链表 [Linked List]:链表是由一组不必相连(不必相连:可以连续也可以不连续)的内存结构(节点),按特定的顺序链接在一起的抽象数据类型。
补充:
抽象数据类型(Abstract Data Type [ADT]):表示数学中抽象出来的一些操作的集合。
内存结构:内存中的结构,如:struct、特殊内存块...等等之类;
2. 数组和链表的区别和优缺点:
数组的优点:
存取速度快
数组的缺点:
事先必须知道数组的长度
插入删除元素很慢
空间通常是有限制的
需要大块连续的内存块
插入删除元素的效率很低
链表优点:
空间没有限制
插入删除元素很快
链表缺点:
存取速度很慢
3. 链表共分几类?
链表常用的有 3 类: 单链表、双向链表、循环链表。
链表的核心操作集有 3 种:插入、删除、查找(遍历)
4. 二叉树
二叉树是树的一种,每个节点最多可具有两个子树,即结点的度最大为 2(结点度:结点拥有的子树数)。
定义:当前根节点的左边全部比根节点小,当前根节点的右边全部比根节点大。
5. 二叉树的种类
(1)斜树
所有结点都只有左子树,或者右子树
(2)满二叉树
所有的分支节点都具有左右节点
(3)完全二叉树
若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h层所有的结点都连续集中在最左边,这就是完全二叉树。
6. 二叉树的一些性质
(1)二叉树第 i 层上的结点数目最多为 2^(i-1) (i≥1)
(2)深度为 h 的二叉树至多有 2^h-1 个结点(h≥1)
(3)包含 n 个结点的二叉树的高度至少为 log2 (n+1)
(4)在任意一棵二叉树中,若终端结点的个数为 n0,度为 2 的结点数为 n2,则 n0=n2+1
7. 二叉树的遍历方式
二叉树的遍历方式,一般分为先序遍历,中序遍历,后序遍历。
(1)先序遍历:先访问根节点,然后访问左节点,最后访问右节点(根->左->右)
(2)中序遍历:先访问左节点,然后访问根节点,最后访问右节点(左->根->右)
(3)后序遍历:先访问左节点,然后访问右节点,最后访问根节点(左->右->根)
先序遍历(根-左-右):1-2-4-8-9-5-10-3-6-7
中序遍历:(左-根-右):8-4-9-2-10-5-1-6-3-7
后序遍历(左-右-根):8-9-4-10-5-2-6-7-3-1
四. Collection接口
1. Collection常用功能
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法
方法如下:
(1)public boolean add(E e) : 把给定的对象添加到当前集合中 。
(2)public void clear() :清空集合中所有的元素。
(3)public boolean remove(E e) : 把给定的对象在当前集合中删除。
(4)public boolean contains(E e) : 判断当前集合中是否包含给定的对象。
(5)public boolean isEmpty() : 判断当前集合是否为空。
(6)public int size() : 返回集合中元素的个数。
(7)public Object[] toArray() : 把集合中的元素,存储到数组中。
五. List接口
1.概述
在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。
2. List接口特点:
(1)它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
(2)它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
(3)集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
3. List接口中常用方法:
(1)public void add(int index, E element) : 将指定的元素,添加到该集合中的指定位置上。
(2)public E get(int index) :返回集合中指定位置的元素。
(3)public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素。
(4)public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
4. List的子类
(1)ArrayList集合
java.util.ArrayList 集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以 ArrayList 是最常用的集合。
许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。
是新的类,是在 JDK 1.2 之后推出的 。性能较高,是采用了异步处理。支持 Iterator、ListIterator 输出。
(2)Vector
与 ArrayList 一样,Vector 本身也属于 List 接口的子类。此类与 ArrayList 类一样,都是 AbstractList 的子类。所以,此时的操作只要是 List 接口的子类就都按照 List 进行操作。
是旧的类是在 JDK 1.0 的时候就定义的。性能较低,是采用了同步处理。除了支持 Iterator、ListIterator 输出,还支持Enumeration 输出。
(3)LinkedList集合
java.util.LinkedList 集合数据存储的结构是链表结构。方便元素添加、删除的集合。
实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可:
(1)public void addFirst(E e) :将指定元素插入此列表的开头。
(2)public void addLast(E e) :将指定元素添加到此列表的结尾。
(3)public E getFirst() :返回此列表的第一个元素。
(4)public E getLast() :返回此列表的最后一个元素。
(5)public E removeFirst() :移除并返回此列表的第一个元素。
(6)public E removeLast() :移除并返回此列表的最后一个元素。
(7)public E pop() :从此列表所表示的堆栈处弹出一个元素。
(8)public void push(E e) :将元素推入此列表所表示的堆栈。
(9)public boolean isEmpty() :如果列表不包含元素,则返回true。
5. Iterator迭代器
5.1. Iterator接口
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator 。 Iterator 接口也是Java集合中的一员,但它与 Collection 、 Map 接口有所不同, Collection 接口与 Map 接口主要用于存储元素,而 Iterator 主要用于迭代访问(即遍历)Collection 中的元素,因此 Iterator 对象也被称为迭代器。
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:
(1)public Iterator iterator() : 获取集合对应的迭代器,用来遍历集合中的元素的。
(2)public E next() :返回迭代的下一个元素。
(3)public boolean hasNext() :如果仍有元素可以迭代,则返回 true。
下面介绍一下迭代的概念:
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
5.2 迭代器的实现原理
当遍历集合时,首先通过调用t集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。
5.3 增强for
增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中不能对集合中的元素进行增删操作。
格式:
for(数据类型 变量名 : 集合or数组名){
//写操作代码
}
它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
6. Set接口
java.util.Set 接口和 java.util.List 接口一样,同样继承自 Collection 接口,它与Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比Collection 接口更加严格了。与 List 接口不同的是, Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
Set 集合有多个子类,这里我们介绍其中的 java.util.HashSet 、 java.util.TreeSet 这两个集合。
6.1 HashSet集合介绍
java.util.HashSet 是 Set 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。 java.util.HashSet 底层的实现其实是一个 java.util.HashMap 支持。
HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode 与 equals 方法。
HashSet 虽然是Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet集合中也是不能去掉重复值的。
6.2 LinkedHashSet
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?在HashSet下面有一个子类 java.util.LinkedHashSet ,它是链表和哈希表组合的一个数据存储结构。
6.3 TreeSet与Comparable
使用TreeSet创建集合时会根据传入的值进行排序,但是如果传入的是对象,则需要我们重写Comparable接口里面的compareTo方法
例子:
TreeSet<String> data = new TreeSet<>(); 如果为对象:Person类里面有name和age两个属性
data.add("B"); TreeSet<Person> data = new TreeSet<>();
data.add("C"); Person p1 = new Person("张三",18);
data.add("D"); Person p2 = new Person("李四",20);
data.add("A"); data.add(p1);
for(String s:data){ data.add(p2);
System.out.print(s); for(Person p:data){
} System.out.print(p);
运行结果为 A B C D }
此时需要在Person类里面重写Comparable接口里面的compareTo方法
先继承Comparable接口,public class Person implements Comparable<Person>
public int compareTo(Person o){
//返回的数据:负数this小/零一样大/整数this大
if(this.age > o.age){
return 1;
}else if(this.age == o.age){
return 0;
}
return -1;
}
这时上面运行的结果为
Person{name='张三',age=18}
Person{name='李四',age=20}
如果age相等则会只输出第一个,因为set集合不能出现重复的元素
六. Map
1. 概述:
Map集合存储的是一个个的键值对数据
Map集合的键(key)不可重复
Map 本身是一个接口,所以一般会使用以下的几个子类:HashMap、TreeMap、Hashtable
2. HashMap
Jdk1.8:
哈希桶中的数据量大于8时,从链表转换为红黑二叉树;
当哈希桶中的数据量减少到6时,从红黑二叉树转换为链表。
散列因子:0.75,意思是当75%的桶存有数据时,对桶进行扩容,长度变为原长度的2倍。
HashMap 本身是属于无序存放的
3. HashMap、Hashtable、ConcurreHashMap的区别
HashMap:线程不安全,效率高
Hashtable:线程安全,效率低
ConcurreHashMap:采用分段锁机制,保证线程安全,效率又比较高。
TreeMap:要求对象所在的类必须实现 Comparable 接口。
LinkedHashMap:保证HashMap有序
4.散列表散列操作
HashMap的实例有两个影响其性能的参数:初始容量和散列因子。初始容量默认为16,负载因子默认为0.75。
在创建HashMap时如果要对初始容量和散列因子进行修改时,应该进行合理的修改
七. 内存泄露
在java的集合中,判断两个对象是否相等的规则是:
(1)判断两个对象的 hashCode 是否相等。如果不相等,认为两个对象也不相等,完毕;如果相等,转入 2
(2)判断两个对象用 equals 运算是否相等。如果不相等,认为两个对象也不相等;如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)。
当一个对象被存进 HashSet 集合后,就不能修改这个对象中的那些参与计算的哈希值的字段了,否则,对象被修改后的哈希值与最初存储进 HashSet 集合中时的哈希值就不同了,在这种情况下,即使在 contains 方法使用该对象的当前引用作为的参数去 HashSet 集合中检索对象,也将返回找不到对象的结果,这也会导致无法从 HashSet 集合中删除当前对象,从而造成内存泄露。
八. JDK9集合新特性
List、Set、Map
of()不可修改的集合。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?