集合
集合框架概述
Java集合框架(Java Collection Framework, JCF)是Java编程语言的一部分,它提供了一种存储和操作一组对象的方式。这个框架的设计目标是提供一组标准的数据结构来帮助开发者更有效地管理数据。
接口与实现
- 接口 定义了集合的行为规范,而实现类则是具体的数据结构。
- 常见的接口包括
Collection
、List
、Set
、Map
等。
1. Collection
Collection
是Java集合框架中最基本的接口,它代表一个不重复元素的集合。它是所有其他集合类型的基础,如List
、Set
和Queue
。
Collection
接口的方法包括添加元素(add
)、删除元素(remove
)、检查是否包含某个元素(contains
)等。
1**、单列集合的顶级接口是谁?双列集合的顶级接口是谁?**
Collection: 每个元素都是一个值
Map: 每个元素都包含两个值
2**、Collection集合有哪两大常用的集合体系,各自有啥特点?**
List系列集合:有序、可重复
Set系列集合:无序、不可重复
java
/*
Collection<E> 这是单列集合的根接口
boolean add(E e) 添加元素
boolean remove(E e) 删除指定的元素 (如有重复删除第一个)
boolean contains(Object obj) 判断集合中是否包含指定元素
int size() 返回集合中元素的个数
boolean isEmpty() 判断集合是否为空
Object[] toArray() 将集合中元素存入一个对象数组并返回
T[] toArray(T[]a) 将集合中元素存入一个指定类型的数组并返回(指定数组长度)
void clear() 清空集合
void addAll(集合) 添加另外一个集合中的元素
*/
public class Demo2 {
public static void main(String[] args) {
//多态创建多列集合
Collection<String> coll = new ArrayList();
//boolean add(E e) 添加元素
coll.add("王菲");
coll.add("顶真");
coll.add("雪豹");
//boolean remove(E e) 删除指定的元素 (如有重复删除第一个)
coll.remove("王菲");
//boolean contains(Object obj) 判断集合中是否包含指定元素
boolean result = coll.contains("顶真");
System.out.println("是否存在" + result);
//int size() 返回集合中元素的个数
System.out.println("集合长度:" + coll.size());
//boolean isEmpty() 判断集合是否为空
System.out.println("判断集合是否为空" + coll.isEmpty());//true为空,false为非空
//Object[] toArray() 将集合中元素存入一个对象数组并返回
// Object[] objects = coll.toArray();
//T[] toArray(T[]a) 将集合中元素存入一个指定类型的数组并返回(指定数组长度)
String[] strings = coll.toArray(new String[coll.size()]);
//void clear() 清空集合
coll.clear();
}
}
1.1 Collection遍历
使用迭代器(Iterator)
当需要在遍历过程中删除集合元素时(使用迭代器自身的remove()
方法),使用迭代器是更好的选择
java
/*
遍历1: 送代器Iterator
单列集合专用遍历方式
Iterator相关方法
Iterator<E> iterator() 获取迭代器对象,默认指向第一个元素
boolean hasNext() 断当前位置是否有元素可以取出 (有返回true,没有返回false)
E next() 返回当前位置的元素,并将送代器后移一位(如果没有元素可以取出了还继续取,会报NoSuchElementException)
固定格式
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
}
*/
public class Demo3 {
public static void main(String[] args) {
//1. 准备一个集合
Collection<String> collection = new ArrayList<>();
collection.add("java");
collection.add("python");
collection.add("c++");
collection.add("c#");
//2. 获取迭代器对象
Iterator<String> iterator = collection.iterator();
//3. 使用迭代器遍历
//3.1使用while,判断是否有下个元素
while (iterator.hasNext()) {
//3.2获取下一个元素
String next = iterator.next();
System.out.println(next);
}
}
}
增强for循环
这是最简单直观的遍历集合的方法。适用于所有实现了Iterable
接口的对象。
java
/*
遍历2: 增强for循环
数组和集合都可以使用
相关格式
for(元素数据类型 变量名 : 数组或者集合){
操作变量
}
注意
1. 在增强for循环中修改数据, 是不会影响数据源的(底层会创建临时变量,来记录容器中的数据)
2. 增强for遍历集合,底层是迭代器遍历集合的逻辑
3. 增强for遍历数组,底层是普通for循环的逻辑
*/
public class Demo4 {
public static void main(String[] args) {
//1. 准备一个集合
Collection<String> collection = new ArrayList<>();
collection.add("java");
collection.add("python");
collection.add("c++");
collection.add("c#");
//2. 使用增强for循环遍历
for (String s : collection) {
System.out.println(s);
}
//增强for循环也可以遍历数组
int[] arr = {1,2,3};
for (int i : arr) {
System.out.println(i);
}
}
}
Lambda表达式(foreach)
java
/*
遍历3: Lambda表达式方式遍历集合
相关格式
collection.forEach(e -> {
System.out.println(e);
});
*/
public class Demo5 {
public static void main(String[] args) {
//1. 准备一个集合
Collection<String> collection = new ArrayList<>();
collection.add("java");
collection.add("python");
collection.add("c++");
collection.add("c#");
//2. Lambda表达式方式遍历集合
collection.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//简化
collection.forEach(s -> {System.out.println(s);});
}
}
遍历删除问题
java
/*
问题
使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误
解决方法
迭代器: 用迭代器自己的删除方法删除数据即可
增强for循环: 暂时无法解决
普通for循环:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i --操作。
*/
public class Demo7 {
public static void main(String[] args) {
//1. 准备一个集合
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("Java入门");
arrayList.add("宁夏枸杞");
arrayList.add("黑枸杞");
arrayList.add("人字拖");
arrayList.add("特级枸杞");
arrayList.add("枸杞子");
//删除所有带枸杞的
//用迭代器删除
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
if (iterator.next().contains("枸杞")) {
iterator.remove();
}
}
//lambda表达式缩写
arrayList.removeIf(s -> s.contains("枸杞"));
//比较原始的方法
// for (int i = arrayList.size() - 1; i > -1; i--) {
// if (arrayList.get(i).contains("枸杞")) {
// arrayList.remove(i);
// }
// }
System.out.println(arrayList);
}
}
1.2 集合的并发修改异常
集合的并发修改异常
使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误。
怎么保证遍历集合同时删除数据时不出bug**?**
使用迭代器遍历集合 : 但用迭代器自己的删除方法删除数据即可。
使用增强 for 循环遍历集合 : 无法解决这个问题
使用普通 for 循环遍历集合: 可以倒着遍历并删除 ; 或者从前往后遍历,但删除元素后做 i -- 操作 。
2.List
List
继承自Collection
接口,并且是一个有序的集合。这意味着元素在列表中的位置(索引)是固定的,并且可以根据索引来访问它们。列表允许重复的元素。
2.1 List集合的特有方法
java
/*
List系列集合的特点
有序的, 可重复
List集合支持索引,所以提供了很多通过索引操作元素的方法
void add(int index,E e) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素 (一般不接收)
E set(int index,E e) 修改指定索引处的元素,返回被修改的元素(一般不接收)
E get(int index) 返回指定索引处的元素
*/
public class Demo1 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("顶真");
list.add("四妹");
list.add("伍陆柒");
list.add("伍陆柒");
list.add("杨永信");
//void add(int index,E e) 在此集合中的指定位置插入指定的元素
list.add(2, "孙悟空");
//E remove(int index) 删除指定索引处的元素,返回被删除的元素 (一般不接收)
list.remove(3);
//E set(int index,E e) 修改指定索引处的元素,返回被修改的元素(一般不接收)
list.set(0, "ikun");
//E get(int index) 返回指定索引处的元素
list.get(1);
System.out.println(list);
}
}
2.2 List集合支持的遍历方式
1. 迭代器
2. 增强for循环
3. Lambda表达式
4. for循环(因为List集合有索引)
java
/*
List支持的遍历方式
1. 迭代器
2. 增强for循环
3. Lambda表达式
4. for循环(因为List集合有索引)
*/
public class Demo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("王五");
//1. 迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
System.out.println(next);
}
//2. 增强for循环
for (String s : list) {
System.out.println(s);
}
//3. Lambda表达式
list.forEach(s -> System.out.println(s));
//4. for循环(因为List集合有索引)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
2.3 ArrayList<E>
2.4 LinkedList集合
LinkedList
是Java集合框架中的一种线性数据结构,它实现了List
接口,并且内部通过双向链表的方式来存储元素。下面是对LinkedList
的一些关键特性的总结:
双向链表结构
LinkedList
中的每个元素都是一个节点,每个节点包含三个部分:数据域、指向下一个节点的引用(后继节点)和指向前一个节点的引用(前驱节点)。这种结构使得LinkedList
非常适合用于频繁插入和删除操作的场景。
插入和删除操作
由于LinkedList
是通过节点链接起来的,因此在列表的任意位置插入或删除元素都非常高效,因为只需要改变前后节点的指针即可,不需要像ArrayList
那样移动大量元素。
不支持随机访问
LinkedList
不支持像ArrayList
那样的随机访问,即通过索引直接访问元素。要访问某个元素,必须从头节点或尾节点开始逐个遍历直到找到目标节点,这导致了访问速度较慢。
主要方法
LinkedList
除了继承自List
的所有方法外,还提供了一些额外的方法来利用其链表特性:
addFirst(E e)
和addLast(E e)
:分别在列表头部和尾部添加元素。removeFirst()
和removeLast()
:分别移除列表的第一个和最后一个元素。getFirst()
和getLast()
:返回但不移除列表的第一个和最后一个元素。peekFirst()
和peekLast()
:类似于getFirst()
和getLast()
,但如果列表为空则返回null
而不是抛出异常。
内存占用
由于每个LinkedList
节点都包含两个额外的指针(前驱和后继),因此相比于ArrayList
,LinkedList
在内存上会有更多的开销。但是,这种开销换取了更好的插入和删除性能。
使用场景
- 当需要频繁地在列表中间插入或删除元素时,
LinkedList
是非常合适的选择。 - 如果你需要使用队列或栈的功能,
LinkedList
也可以作为这些数据结构的基础实现。
性能考虑
- 如果你需要频繁地访问列表中的随机元素,则
ArrayList
可能比LinkedList
更适合。 - 如果需要在一个非常大的列表中进行大量的插入和删除操作,
LinkedList
将表现得更好。
实现细节
LinkedList
内部有一个Node
类来表示链表中的每个节点,这些节点通过next
和prev
字段相互连接。
总的来说,LinkedList
是一种非常适合需要频繁进行插入和删除操作的数据结构,但由于它的非连续存储特性,在需要随机访问元素时不如ArrayList
高效。选择使用哪种数据结构取决于具体的应用场景和性能要求。
java
/*
LinkedList
底层数据结构:
基于双向链表实现(内存地址不连续,每个元素记录自己的前后元素)
特点:
1. 查询速度慢
2. 增删效率高
3. 对于首尾元素进行增删改查的速度都是极快的
应用场景:
1. 用来设计队列(两端开口,类似于一个管道,先进先出)
只操作首尾元素, 尾部添加, 首部删除
2. 用来设计栈(一段开口,类似于弹夹,先进后出)
*/
public class Demo4 {
public static void main(String[] args) {
makeQueue();
//makeStack();
}
/*
队列: 两端开口,特点是先进先出(排队)
从队列后端入队列: addLast 方法
从队列前端出队列: removeFirst方法
*/
public static void makeQueue() {
LinkedList<String> queue = new LinkedList<>();
//从队列后端入队列: addLast方法
queue.addLast("第1位顾客");
queue.addLast("第2位顾客");
queue.addLast("第3位顾客");
queue.addLast("第4位顾客");
System.out.println(queue);
//从队列前端出队列: removeFirst方法
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue);
System.out.println("---------");
}
/*
栈: 顶端开口的结构,特点是先进后出
进栈/压栈: push方法(底层封装了addFirst 方法)
出栈/弹栈: pop方法底 (底层封装了removeFirst方法)
*/
public static void makeStack() {
LinkedList<String> stack = new LinkedList<>();
//进栈/压栈: push方法(底层封装了addFirst 方法)
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
System.out.println(stack);
//出栈/弹栈: pop方法底(底层封装了removeFirst方法)
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack);
}
}
3. Set
Set是Java集合框架中的一个重要接口,它继承自Collection接口,并且是一个不允许包含重复元素的集合。 Set确保了集合内的每个元素都是唯一的,这在很多情况下都是非常有用的特性。以下是Set集合的一些关键点:
3.1 特性
不允许重复:Set集合中的元素必须是唯一的,如果尝试添加一个已存在的元素,添加操作会被忽略。
无序:Set本身没有指定元素的顺序,除非使用特定类型的Set实现(如LinkedHashSet)。
java
/*
List系列集合的特点
无序, 不可重复
子类
HashSet: 完美继承
LinkedHashSet: 存取有序
TreeSet: 可以排序
*/
public class Demo1 {
public static void main(String[] args) {
testHashSet();
System.out.println("==========");
testLinkedHashSet();
System.out.println("==========");
testTreeSet();
}
//HashSet: 无序、没有索引、不可重复
private static void testHashSet() {
HashSet<Integer> hashSet = new HashSet<>();
hashSet.add(44);
hashSet.add(33);
hashSet.add(11);
hashSet.add(22);
hashSet.add(22);
System.out.println(hashSet);
}
//LinkedHashSet: 存取有序、没有索引、不可重复
private static void testLinkedHashSet() {
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add(44);
linkedHashSet.add(33);
linkedHashSet.add(11);
linkedHashSet.add(22);
linkedHashSet.add(22);
System.out.println(linkedHashSet);
}
//TressSet: 排序、没有索引、不可重复
private static void testTreeSet() {
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(44);
treeSet.add(33);
treeSet.add(11);
treeSet.add(22);
treeSet.add(22);
System.out.println(treeSet);
}
}
3.2 实现
Set有多种实现方式,每种实现都有其独特的特点和应用场景:
HashSet
内部实现:基于哈希表。
特点:提供了最快的添加、删除和查找操作,因为这些操作平均时间复杂度为O(1)。
无序:元素的存储和迭代顺序与插入顺序无关。
使用场景:当你主要关心的是元素的唯一性并且不需要保证任何特定的顺序时,HashSet是最合适的选择。
java
/*
哈希值
就是一int值,Java每个对象都可以通过hashCode方法,获取自己的哈希值
哈希值特点
同一个对象多次调用hashCode方法,返回的哈希值是相同的;
不同的对象,他们哈希值大几率不相同,但是也有可能会相同(哈希碰撞)
Object的hashCode方法根据"对象地址值"计算哈希值,子类重写后的hashCode方法可以根据"对象属性值"计算哈希值
使用场景
HashSet集合判定两个对象的标准就是两个对象的hash值是否一致, 因此我们经常重写hashcode实现集合中对象去重
*/
public class Demo2 {
public static void main(String[] args) {
// Student stu = new Student("张三", 18);
// int code1 = stu.hashCode();
// int code2 = stu.hashCode();
// //同一个对象多次调用hashCode方法,返回的哈希值是相同的;
// System.out.println(code1);//1324119927 重写hashCode之后:24022538
// System.out.println(code2);//1324119927 重写hashCode之后:24022538
// //2.不同的对象,他们哈希值大概率不相同,但是也有可能会相同(哈希碰撞)
// Student student = new Student("张三", 18);
// int code3 = student.hashCode();
// System.out.println(code3);//990368553 重写hashCode之后:24022538
//通过HashSet集合,存储学生数据(去重)
HashSet<Student> set = new HashSet<>();
//构造几个学生的对象,存入set
Student student1 = new Student("老张", 18);
Student student2 = new Student("老李", 23);
Student student3 = new Student("老张", 18);
System.out.println(student1);
System.out.println(student3);
set.add(student1);
set.add(student2);
set.add(student3);
System.out.println(set);
}
}
class Student{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
TreeSet
内部实现:基于红黑树(一种自平衡二叉搜索树)。
特点:元素自动按照自然顺序(或由比较器定义的顺序)进行排序。
有序:提供了first(), last(), headSet(), tailSet()等方法来访问子集。
使用场景:当需要元素保持排序状态时,TreeSet非常有用。
java
/*
TreeSet 可排序、不重复、无索引
底层基于红黑树实现排序,排序规则认为属性是相同的对象则不存
TreeSet的排序
对于数值型Integer、Double,默认按照数值升序排列;
对于String类型数据,默认按照字典排序
对于自定义类,默认是无法排序的,需要我们指定排序规则
自然排序:自定义类实现Comparable接口,重写compareTo方法,指定排序规则
比较器排序:写在TreeSet构造参数中传递Comparator比较器对象,重写compare方法,指定排序规则
需求
使用TreeSet存储教师对象,重复对象不存,并且用两种方式按照年龄升序排列
*/
public class Demo4 {
public static void main(String[] args) {
//创建TreeSet
TreeSet<Teacher> treeSet = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
return o1.getAge() - o2.getAge();
}
});
//添加学生
treeSet.add(new Teacher("张三", 19));
treeSet.add(new Teacher("李四", 18));
treeSet.add(new Teacher("王五", 20));
treeSet.add(new Teacher("赵六", 17));
treeSet.add(new Teacher("赵六", 17));
//打印
for (Teacher teacher : treeSet) {
System.out.println(teacher);
}
}
}
class Teacher {
private String name;
private int age;
public Teacher() {
}
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Teacher teacher = (Teacher) o;
return age == teacher.age && Objects.equals(name, teacher.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
LinkedHashSet
内部实现:结合了HashSet的快速特性和TreeSet的有序特性。
特点:保持了元素的插入顺序,同时也确保了元素的唯一性。
有序:迭代顺序与元素被添加到集合中的顺序相同。
使用场景:当你需要元素唯一并且要保留插入顺序时,LinkedHashSet是一个不错的选择。
3.3 方法
Set接口继承了Collection接口中的方法,并且添加了一些自己的方法,例如:
add(E e):添加一个元素到集合中,如果该元素已经存在则返回false。
contains(Object o):检查集合是否包含特定的元素。
remove(Object o):从集合中移除一个特定的元素。
size():返回集合中元素的数量。 isEmpty():判断集合是否为空。
clear():清空整个集合。
3.4 使用场景
数据去重:当需要去除列表或其他集合中的重复项时,可以先将其转换为Set再转回原集合类型。
唯一性检查:当你需要确保某些数据结构中元素的唯一性时,Set是一个理想的选择。
快速查找:如果你需要快速地查找一个元素是否存在,可以使用HashSet。
排序需求:如果你需要一个自动排序的集合,那么TreeSet将是合适的选择。
插入顺序敏感:如果你需要一个既保持元素唯一性又保持插入顺序的集合,可以选择LinkedHashSet。
3.5 注意事项
由于Set不允许重复元素,所以在添加元素之前会进行相等性检查。
对于HashSet来说,添加到集合中的元素需要正确地重写equals和hashCode方法以确保一致性。
TreeSet需要元素实现Comparable接口或提供一个Comparator来定义排序规则。
总之,Set提供了一种非常有用的机制来处理需要元素唯一性的场景,而不同的实现提供了灵活性以适应各种不同的需求。
4. 总结
1**、如果希望记住元素的添加顺序,需要存储重复的元素****,又要频繁的根据索引查询数据?**
用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)
2**、如果希望记住元素的添加顺序****,且增删首尾数据的情况较多?**
用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的。
3**、如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?**
用HashSet集合(无序,不重复,无索引),底层基于哈希表实现的。 (常用)
4**、如果希望记住元素的添加顺序****,也没有重复元素需要存储,且希望****增删改查都快?**
用LinkedHashSet集合(有序,不重复,无索引), 底层基于哈希表和双链表。
5**、如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?**
用TreeSet集合,基于红黑树实现。