day06-集合-CollectionListSet
一、集合
Collection集合特点:
list系列集合:有序、可重复
ArrayList、LinekdList
Set系列集合:无序、不重复
HashSet
LinkedHashSet:存取有序
TreeSet:可排序
1.1 Collection的常见方法
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("刮风");
coll.add("刮风");
System.out.println(coll);
//boolean remove(E e) 删除指定的元素 (如有重复删除第一个)
coll.remove("刮风");
System.out.println(coll);
//boolean contains(Object obj) 判断集合中是否包含指定元素
boolean contains = coll.contains("下雪");
System.out.println("contains=" + contains);
//int size() 返回集合中元素的个数
int size = coll.size();
System.out.println("size=" + size);
//boolean isEmpty() 判断集合是否为空
boolean empty = coll.isEmpty();
System.out.println("empty=" + empty);//空-->true;否则-->false
//Object[] toArray() 将集合中元素存入一个对象数组并返回
Object[] objects = coll.toArray();
System.out.println(Arrays.toString(objects));
//T[] toArray(T[]a) 将集合中元素存入一个指定类型的数组并返回(指定数组长度)
String[] strings = coll.toArray(new String[coll.size()]);
System.out.println(Arrays.toString(strings));
//void clear() 清空集合
coll.clear();
System.out.println(coll);
}
}
运行结果:
[下雨, 下雪, 刮风, 刮风]
[下雨, 下雪, 刮风]
contains=true
size=3
empty=false
[下雨, 下雪, 刮风]
[下雨, 下雪, 刮风]
[]
1.2 Collection的遍历方式
1.2.1 迭代器Iterator
遍历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. 使用迭代器遍历
while(iterator.hasNext()){
String s = iterator.next();
System.out.println(s);
}
}
}
1.2.2 增强for
遍历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);
}
}
}
1.2.3 Lambda表达式方式
遍历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((e)->{
//遍历集合中的元素e
System.out.println(e);
});
}
}
1.3 删除集合元素
问题 使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。 由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误 解决方法 迭代器: 用迭代器自己的删除方法删除数据即可(iterator.remove();) 增强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()){
//获取元素
String str = iterator.next();
if(str.contains("枸杞")){
//通过迭代器删除
iterator.remove();//从迭代器中删除当前元素
}
}
System.out.println(arrayList);//[Java入门, 人字拖]
}
}
1.4 集合存储对象的原理
二、List集合
2.1 List集合常用方法
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集合
List<String> list = new ArrayList<>();
list.add("张三");//0
list.add("李四");//1
list.add("王五");//2
list.add("郭七");//3
System.out.println(list);
//void add(int index,E e) 在此集合中的指定位置插入指定的元素
list.add(2, "赵六");
System.out.println(list);
//E remove(int index) 删除指定索引处的元素,返回被删除的元素 (一般不接收)
list.remove(3);
System.out.println(list);
//E set(int index,E e) 修改指定索引处的元素,返回被修改的元素(一般不接收)
list.set(1,"钱八");
System.out.println(list);
//E get(int index) 返回指定索引处的元素
list.get(1);
System.out.println(list.get(1));
System.out.println(list);
}
}
2.2 list集合遍历
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 s = iterator.next();
System.out.println(s);
}
System.out.println("===========================");
//2. 增强for循环
for (String s : list) {
System.out.println(s);
}
System.out.println("===========================");
//3. Lambda表达式
list.forEach(e ->{
System.out.println(e);
});
System.out.println("===========================");
//4. for循环(因为List集合有索引)
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
}
}
2.3 ArrayList
2.3.1 ArrayList底层原理
ArrayList 底层数据结构: 基于数组实现 特点:(查询快,增删慢) 1. 查询速度快(通过索引直接定位) 2. 增删效率低(增删的时候,需要移动增删元素后面的元素, 有时还需要进行扩容) 适用场景: 1. ArrayList适合于根据索引查询数据, 或者数据量不大的场景 2. ArrayList不适合于数据量大, 同时又要频繁进行增删操作的场景 底层原理: 1. 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组 ArrayList<String> list = new ArrayList(); 2. 添加第一个元素时,底层会创建一个新的长度为10的数组 list.add("a"); 3. 存满时,会扩容1.5倍 比如存入第11个元素的时候, 长度会扩容到15 4. 如果一次添加多个元素, 1.5倍还放不下, 则新创建数组的长度以实际为准 比如原来是10个元素,现在又要存入10个, 则长度会扩容到20
2.3.2 LinkedList集合的底层原理
LinkedList 底层数据结构: 基于双向链表实现(内存地址不连续,每个元素记录自己的前后元素) 特点: 1. 查询速度慢 2. 增删效率高 3. 对于首尾元素进行增删改查的速度都是极快的 应用场景: 1. 用来设计队列(两端开口,类似于一个管道,先进先出) 只操作首尾元素, 尾部添加, 首部删除 2. 用来设计栈(一段开口,类似于弹夹,先进后出)
public class Demo4 {
public static void main(String[] args) {
makeQueue();
System.out.println("----------------");
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);
}
运行结果:
[第1位顾客, 第2位顾客, 第3位顾客, 第4位顾客]
第1位顾客
第2位顾客
[第3位顾客, 第4位顾客]
/*
栈: 顶端开口的结构,特点是先进后出
进栈/压栈: 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);
}
运行结果:
[第4颗子弹, 第3颗子弹, 第2颗子弹, 第1颗子弹]
第4颗子弹
第3颗子弹
[第2颗子弹, 第1颗子弹]
}
三、Set系列集合
1、Set系列集合的特点是啥?
无序、不重复
2、Set集合的常见实现类有哪几个?各自有啥特点?
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.1 HashSet
3.1.1 哈希值
哈希值 就是一int值,Java每个对象都可以通过hashCode方法,获取自己的哈希值 public int hashCode():返回对象的哈希码值。 哈希值特点 1.同一个对象多次调用hashCode方法,返回的哈希值是相同的; 2.不同的对象,他们哈希值大几率不相同,但是也有可能会相同(哈希碰撞) 3.Object的hashCode方法根据"对象地址值"计算哈希值,子类重写后的hashCode方法可以根据"对象属性值"计算哈希值 使用场景 HashSet集合判定两个对象的标准就是两个对象的hash值是否一致, 因此我们经常重写hashcode实现集合中对象去重
public class Demo2 {
public static void main(String[] args) {
//创建一个学生对象,获取对象的哈希值
//1.同一个对象多次调用hashCode方法,返回的哈希值是相同的;
Student student = new Student("Tom", 15);
int code = student.hashCode();
System.out.println(code);
int code1 = student.hashCode();
System.out.println(code1);
//2.不同的对象,他们哈希值大几率不相同,但是也有可能会相同(哈希碰撞)
Student student1 = new Student("Jerry", 16);
int code2 = student1.hashCode();
System.out.println(code2);
//3.Object的hashCode方法根据"对象地址值"计算哈希值
//子类重写后的hashCode方法可以根据"对象属性值"计算哈希值
Student student2 = new Student("Jerry", 16);
int code3 = student2.hashCode();
System.out.println(code3);
}
}
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 +
'}';
}
//重写hashCode方法,根据对象的属性值计算哈希值
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
3.1.2 Set集合去重
需求:
创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是同一个对象
分析
①定义学生类,创建HashSet集合对象, 创建学生对象
②把学生添加到集合
③在学生类中重写两个方法,hashCode()和equals(),自动生成即可
public class Demo2 {
public static void main(String[] args) {
//通过HashSet集合,存储学生数据(去重:需要重写hashCode()和equals())
HashSet<Student> students = new HashSet<>();
//构造几个学生对象,存入set集合
Student student1 = new Student("Tom", 15);
Student student2 = new Student("Tom", 15);
Student student3 = new Student("Tim", 19);
students.add(student1);
students.add(student2);
students.add(student3);
System.out.println(students);
}
}
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 +
'}';
}
//重写hashCode方法,根据对象的属性值计算哈希值
@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);
}
}
3.1.3 HashSet集合的底层原理
基于哈希表实现
哈希表是一种增删改查数据,性能都较好的数据结构。
1、HashSet集合的底层原理是什么样的?
基于哈希表实现的。
JDK8之前的,哈希表:底层使用数组+链表组成
JDK8开始后,哈希表:底层采用数组+链表+红黑树组成。
2、HashSet集合利用哈希表操作数据的详细流程是咋回事?
① 创建一个默认长度16,默认加载因为0.75的数组,数组名table
② 根据元素的哈希值跟数组的长度计算出应存入的位置
③ 判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素,
则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组.
④ 当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的1倍
3.2 LinkedHashSet
3.3 TreeSet
TreeSet 可排序(默认升序排序 ,按照元素的大小,由小到大排序)、不重复、无索引 底层基于红黑树实现排序,排序规则认为属性是相同的对象则不存 TreeSet的排序 对于数值型Integer、Double,默认按照数值升序排列; 对于String类型数据,默认按照字典排序 对于自定义类,默认是无法排序的,需要我们指定排序规则 1.自然排序:自定义类实现Comparable接口,重写compareTo方法,指定排序规则 2.比较器排序:写在TreeSet构造参数中传递Comparator比较器对象,重写compare方法,指定排序规则 需求 使用TreeSet存储教师对象,重复对象不存,并且用两种方式按照年龄升序排列
//比较器排序:写在TreeSet构造参数中传递Comparator比较器对象,重写compare方法,指定排序规则
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 +
'}';
}
}
运行结果:
Teacher{name='赵六', age=17}
Teacher{name='李四', age=18}
Teacher{name='张三', age=19}
Teacher{name='王五', age=20}
//自然排序:自定义类实现Comparable接口,重写compareTo方法,指定排序规则
public class Demo5 {
public static void main(String[] args) {
//创建TreeSet
TreeSet<Teacher1> treeSet = new TreeSet<>();
//添加学生
treeSet.add(new Teacher1("张三", 19));
treeSet.add(new Teacher1("李四", 18));
treeSet.add(new Teacher1("王五", 20));
treeSet.add(new Teacher1("赵六", 17));
treeSet.add(new Teacher1("赵六", 17));
//打印
for (Teacher1 teacher : treeSet) {
System.out.println(teacher);
}
}
}
class Teacher1 implements Comparable<Teacher1>{
private String name;
private int age;
public Teacher1() {
}
public Teacher1(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 int compareTo(Teacher1 o) {
return this.age - o.age;
}
}
运行结果:
Teacher{name='赵六', age=17}
Teacher{name='李四', age=18}
Teacher{name='张三', age=19}
Teacher{name='王五', age=20}