Day18-C:\Users\Lenovo\Desktop\note\code\JavaSE\Basic\src\com\CollectionandMap
Collection
-
add
-
node
-
增强for
-
Lambda
import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.function.Consumer; public class CollectionDemo01 { public static void main(String[] args) { Collection<String> c = new ArrayList<>(); c.add("张三"); c.add("李四"); c.add("王五"); c.add("赵六"); System.out.println(Arrays.toString(c.toArray())); //使用迭代器遍历集合 Iterator<String> it = c.iterator();//默认站在第一个元素处 System.out.println(it.next());//取数据并移动到下一处 System.out.println(it.next()); System.out.println(it.next()); System.out.println(it.next()); //System.out.println(it.next());//出现异常 Iterator<String> its = c.iterator(); while (its.hasNext()) { String s1 = its.next(); System.out.println(s1); } //增强for遍历集合集合 for(String s2: c){ System.out.println("增强for遍历集合"+s2);//增强for遍历集合本质上是迭代器遍历集合的简化版 } String[] name = {"张三","李四","王五","赵六"}; for (String s3: name){ System.out.println("增强for遍历数组"+s3); } //s.for 然后回车就可以调用增强for方法 /*c.forEach(new Consumer<String>() {//创建Consumer接口的匿名内部类,重写方法 @Override public void accept(String s) { System.out.println(s); } /* 源码 default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } 类 '派生自 Consumer 的匿名类' 必须在 'Consumer' 中实现 abstract 方法 'accept(T)' */ c.forEach((String s) ->{ System.out.println(s); }); //-> 是lambda 表达式的语法符号,用于简化函数式接口(只有一个抽象方法的接口)的实现。它将左侧的参数列表与右侧的方法体连接起来,相当于 “把参数传递给方法体执行”。 c.forEach(ele->{// lambda 表达式 s -> System.out.println(s) 是 Consumer 接口的实现,其中的 s 就是对 accept 方法参数 t 的 “简写”(变量名可以自定义,比如写成 x 或 elem 都可以)。 System.out.println(ele); }); c.forEach(s-> System.out.println(s));//println是方法,out是对象 c.forEach(System.out::println); //:: 是方法引用(Method Reference) 的语法符号 //由于 forEach 在遍历过程中会依次把每个元素传入 accept 方法,因此 s 自然就代表了集合 c 在当前遍历步骤中的元素。 //如果 forEach 要求 Consumer<String>,则只能传入 “专门处理 String 的消费逻辑”。 //但用 Consumer<? super String> 后,不仅能传入 Consumer<String>,还能传入 Consumer<Object>(因为 Object 是 String 的父类)。比如 Consumer<Object> 中可能有一个通用的打印逻辑(如 System.out::println,它本质上是 Consumer<Object>),这样的逻辑同样能处理 String 元素。 } }
import java.util.ArrayList;
import java.util.Collection;
public interface collectionDemo02 {
public static void main(String[] args) {
//1.创建一个集合容器负责储存多部电影对象
Collection<Movie> movies = new ArrayList<>();
movies.add(new Movie("《肖申克的救赎》",9.7,"罗宾斯"));
movies.add(new Movie("《霸王别姬》",9.6,"张国荣"));
movies.add(new Movie("《阿甘正传》",9.5,"汤姆·汉克斯"));
//集合存储对象本质上是存储对象的地址信息,直接打印出来的是地址
System.out.println(movies);
movies.forEach(System.out::println);
for (Movie movie : movies) {
System.out.println("电影名:"+movie.getName());
System.out.println("评分:"+movie.getScore());
System.out.println("主演:"+movie.getActors());
System.out.println();
}
}
}
List
- 特点:有序可重复有索引
- 方法:add,remove,set,get
- 遍历方式:支持for循环遍历
- ArrayList和LinkedList存贮和组织数据结构的方式不同
ArrayList
基于数组实现的,根据索引,查询速度较快,查询任意数据耗时相同;但是删除效率低,需要把后面数据前移;添加效率极低,先把后面数据后移,再添加元素
底层原理
-
利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
List<String> list = new ArrayList<>();//(经典代码)这里右边不能再用List,要用他的实现类ArrayList<> list.add("蜘蛛精"); -
添加第一个元素时,底层会创建一个新的长度为10的数组,然后让size从第一个索引(0)指向第二个索引(1),size索引下标等于元素个数等于下次存入位置
-
当存储第11个数据时,会将原来的数组进行扩容,并将数组中的元素迁移到新数组
-
如果一次添加多个元素,1.5倍还放不下,则新创建数组长度以实际为准,例如将list2中的21个元素用addAll方法倒入只有长度为10的list1时,会直接创建到21个空间
适合场景
- 根据随机索引取数据或数据量不是很大时
- 不适合数据量大且需要频繁增删操作
LinkedList
通过双向链表存储
链表特点:
查询慢,按照索引查询也得从头查
链表增删相对较快
LinkedList设计:排队系统,栈
排队:
import java.util.LinkedList;
import java.util.List;
public class ListTest2 {
public static void main(String[] args) {
//1.创建一个队列
LinkedList<String> queue = new LinkedList<>();//这里不要用多态,因为后面需要调用LinkedList的专用方法
queue.addLast("第一号人");
queue.addLast("第二号人");
queue.addLast("第三号人");
queue.addLast("第四号人");
queue.addLast("第五号人");
System.out.println(queue);
//出队
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue);
}
}
栈(先进后出,后进先出)类似于手枪弹夹,只在首部增删元素
数据进入栈模型的过程为:压/进栈(push)
数据离开栈模型的过程称为:弹/出栈(pop)
//2.创建一个栈对象
LinkedList<String> stack = new LinkedList<>();
//压栈(push方法)
stack.addFirst("第一颗子弹");
stack.push("第二颗子弹");
stack.push("第三颗子弹");
stack.addFirst("第四颗子弹");
System.out.println(stack);
//出栈(pop方法)
System.out.println(stack.removeFirst());
System.out.println(stack.pop());
System.out.println(stack);
}
Set
无序(指的是添加数据和获取数据的顺序不一致),不重复,无索引
package com.CollectionandMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
public class SetTest1 {
public static void main(String[] args) {
//创建一个Set集合的对象
//Set<Integer> set = new HashSet<>();//创建了一个HashSet的集合对象,一行经典代码,无序的顺序一旦定下来了就不会改变,多运行几次不能改变他的顺序
//Set<Integer> set = new LinkedHashSet<>();//创建了一个LinkedHashSet的集合对象,一行经典代码,有序:指的是添加顺序和输出顺序的一致
Set<Integer> set = new TreeSet<>();//创建了一个LinkedHashSet的集合对象,一行经典代码,有序:默认可排序,默认升序[555, 666, 777, 888]
set.add(666);
set.add(555);
set.add(555);
set.add(888);
set.add(888);
set.add(777);
set.add(777);
System.out.println(set);
}
}
-
HashSet:无序,不重复,无索引
-
LinkedHashSet(HashSet的子类):有序,不重复,无索引
-
TreeSet:排序,不重复,无索引
package com.CollectionandMap; public class SetTest2 { public static void main(String[] args) { Student s1 = new Student("蜘蛛精",25,169.5); Student s2 = new Student("紫霞",22,166.5); System.out.println(s1.hashCode()); System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); String str1 = new String("abc"); String str2 = new String("acD");//哈希碰撞 System.out.println(str1.hashCode()); System.out.println(str2.hashCode()); } }
HashSet
哈希值就是一个int类型的数值,java中每一个对象都有哈希值
Java中的所有对象,都可以调用Object类提供的hashCode方法,返回该对象自己的哈希值
public int hashCode() :返回对象的哈希码值
不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)
基于哈希表实现,哈希表是一种增删改查数据,性能都较好的数据结构
哈希表
JDK8之前,哈希表 = 数组+链表
哈希表的核心是通过哈希函数将元素映射到数组(哈希桶)的索引位置。但随着元素不断插入:
-
哈希冲突概率增加:当元素数量接近或超过哈希表容量时,多个元素会被映射到同一个桶中(哈希冲突),导致桶中的链表 / 红黑树过长,查询、插入、删除的时间复杂度从理想的 O (1) 退化到 O (n) 或 O (log n)。
-
负载因子过高
:哈希表用「负载因子(load factor)」衡量元素填充程度,公式为:负载因子 = 元素数量 / 哈希表容量负载因子越高,冲突概率越大。Java 中HashMap的默认负载因子是 0.75,即当元素数量达到容量的 75% 时,触发扩容。
二、扩容的核心步骤
扩容的本质是「扩大哈希表容量 + 重新分布元素」,以 Java
HashMap为例,步骤如下:-
确定新容量:通常将容量扩大为原来的 2 倍(保证容量是 2 的幂,简化哈希计算)。例如,原容量为 16 时,扩容后为 32。
-
创建新的哈希桶数组:容量为新容量,用于存储重新分布后的元素。
-
重新哈希(rehash)并迁移元素:
遍历原哈希表中的所有元素(包括链表 / 红黑树中的节点),根据新容量重新计算每个元素的哈希值(即新的索引位置),然后将元素迁移到新数组的对应位置。
- 为什么要重新计算哈希?因为索引计算依赖容量(如
index = hash & (容量 - 1)),容量变了,索引也会变化。
- 为什么要重新计算哈希?因为索引计算依赖容量(如
-
替换引用:将哈希表的底层数组引用指向新数组,释放原数组内存。
-
JDK8开始,哈希表 = 数组+链表+红黑树
当链表长度超过8,且数组长度>=64,自动将链表转成红黑树
二叉树
如果希望Set集合认为两个内容一样的对象是重复的,必须重写对象的hashCode()方法和equals()}方法
package com.CollectionandMap;
import java.util.HashSet;
import java.util.Set;
public class SetTest3 {
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
public static void main(String[] args) {
Set<Student> students = new HashSet<>();
Student s1 = new Student("至尊宝", 28, 169.6);
Student s2 = new Student("蜘蛛精", 23, 169.6);
Student s3 = new Student("蜘蛛精", 23, 169.6);
Student s4 = new Student("牛魔王", 48, 169.6);
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
System.out.println(students);//内容一样的两个Student类的对象存入到HashSet集合中去,HashSet集合是不能去重复的!
//如果希望Set集合认为两个内容一样的对象是重复的,必须重写对象的hashCode()方法和equals()}方法,要在Student类里面重写
//右键->生成
}
}
重写如下
package com.CollectionandMap;
import java.util.Objects;
public class Student {
private String name;
private int age;
private double weight;
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;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public Student() {
}
public Student(String name, int age, double weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
'}';
}
@Override
public boolean equals(Object o) {
//if (this == o) return true;//检查地址是否相同,即是否为同一个对象
if (o == null || getClass() != o.getClass()) return false;//非空判断,getClass() != o.getClass() 检查两个对象是否属于同一个类。
Student student = (Student) o;
return age == student.age && Double.compare(weight, student.weight) == 0 && Objects.equals(name, student.name);//简化版
//&& 是逻辑 “与” 运算符,只有当所有条件都满足时,整个表达式的结果才为 true,此时 return 语句会返回 true;只要有一个条件不满足(结果为 false),整个表达式就为 false,return 会返回 false。
}
//只要两个对象内容要用,返回的哈希值就是一样的
@Override
public int hashCode() {
return Objects.hash(name, age, weight);//Objects.hash(name, age, weight) 是 Java 提供的工具方法,用于根据传入的多个参数计算哈希值。
}
}

浙公网安备 33010602011771号