11 集合
11 集合
11.1 介绍
11.1.1 算法和数据结构
程序=数据结构+算法
一、算法:
可以解决具体的问题(解决流程),有设计解决的具体的流程(算法1、算法2),有评价这个算法的具体指标(时间复杂度、空间复杂度)
二、数据结构:
1.逻辑结构,思想上的结构。厨房、卧室、卫生间。线性表(数组、链表)、图、树、栈、队列。
2.物理结构,真实的结构。钢筋混凝土+牛顿力学。紧密结构(顺序结构)、跳转结构(链式结构)。
数组(紧密结构),优点:寻址快查找快,缺点:删除和增加元素效率低
链表(跳转结构)(单向链表/双向链表/循环链表),优点:删除和插入元素效率高,缺点:查询元素效率低
11.1.2 引入集合
数组、集合都是对多个数据进行存储操作的,简称为容器。
这里的存储指的是内存层面的存储,而不是持久化存储。
数组的特点:(1)一旦指定了长度,长度不可以更改;(2)一旦声明了类型,只能存放这个类型的数据。
数组的缺点:
因为上面的缺点,引入新的数据结构->集合。
11.1.3 简要集合结构图
Collection、Map

图片来自网络
11.2 Collection
package com.liweixiao.test01;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* @author:LiWeixiao
* @date:2022/12/28
* @description:
*/
public class Test01 {
public static void main(String[] args) {
/*
Collection接口的常用方法:
增加:add、addAll
删除:clear、remove
修改:
查看:iterator、size
判断:contains、equals、isEmpty
*/
//创建对象
Collection col = new ArrayList();
//调用方法
col.add(18);//集合只能存放引用数据类型的数据,不能是基本数据类型;基本数据类型自动装箱,对应包装类
col.add(12);
col.add(11);
col.add(17);
System.out.println(col);
List list = Arrays.asList(new Integer[]{11, 15, 3, 7, 1});
col.addAll(list);
System.out.println(col);
//col.clear();//清空集合
System.out.println(col);
System.out.println("集合中元素的数量:"+col.size());
System.out.println("集合是否为空:"+col.isEmpty());
boolean isRemove = col.remove(15);
System.out.println(col);
System.out.println("是否删除:"+isRemove);
Collection col2 = new ArrayList();
col2.add(18);
col2.add(12);
col2.add(11);
col2.add(17);
Collection col3 = new ArrayList();
col3.add(18);
col3.add(12);
col3.add(11);
col3.add(17);
System.out.println(col2.equals(col3));//比较集合内的值,true
System.out.println(col2 == col3);//==比较的是地址,false
boolean isContains = col3.contains(17);
System.out.println("是否包含元素:"+isContains);
}
}
遍历方式:1.增强for循环,2.迭代器
package com.liweixiao.test01;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* @author:LiWeixiao
* @date:2022/12/28
* @description:
*/
public class Test02 {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(18);
col.add(12);
col.add(11);
col.add(17);
col.add("abc");
col.add(1.01);
//对集合遍历
//方式1:普通for循环
/*for (int i = 0; i < col.size(); i++) {
col.
}*/
//方式2:增强for循环
for (Object o:col) {
System.out.println(o);
}
System.out.println("------------------");
//方式3:iterator
Iterator it = col.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
}
11.2.1 List
遍历方式:1普通for循环,2增强for循环,3迭代器
package com.liweixiao.test01;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author:LiWeixiao
* @date:2022/12/28
* @description:
*/
public class Test03 {
public static void main(String[] args) {
/*
List常用方法
增加:add
删除:remove(int)、remove(object)
修改:set
查看:get
判断:
*/
List list = new ArrayList();
list.add(13);
list.add(17);
list.add(6);
list.add(-1);
list.add(2);
list.add("abc");
System.out.println(list);
list.add(3,66);
System.out.println(list);
list.set(3,77);
System.out.println(list);
list.remove(2);//在集合中存入的是Integer类型数据,调用remove方法是int
System.out.println(list);
list.remove("abc");
System.out.println(list);
Object o = list.get(0);
System.out.println(o);
//方式1:普通for循环
System.out.println("--------------");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//方式2:增强for循环
System.out.println("//////////////");
for (Object obj :
list) {
System.out.println(obj);
}
//方式3:迭代器
System.out.println("**********");
Iterator it = list.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
}
11.2.2 ArrayList实现类
class ArrayList
class AbstractList
集团创始人承认了这个失误。
当数组中的10个位置都满了,就开始进行数组的扩容,扩容长度为 原数组的1.5倍。
JDK1.7和1.8的区别
package com.liweixiao.test02;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* @author:LiWeixiao
* @date:2022/12/30
* @description:
*/
public class Test01 {
public static void main(String[] args) {
//接口=实现类
Collection col=new ArrayList();
List list=new ArrayList();
//直接创建实现类对象
ArrayList al = new ArrayList();
System.out.println(al.add("abc"));//true
al.add("bcd");
System.out.println(al);
}
}
11.2.3 Vector实现类
联系:底层都是数组的扩容
区别:
ArrayList底层扩容长度为原数组的1.5倍,线程不安全,效率高
Vector底层扩容长度为原数组的2倍,线程安全,效率低(已淘汰)
11.3 泛型
泛型就相当于标签,形式<>,JDK1.5之后
11.3.1 泛型介绍
package com.liweixiao.test01;
import java.util.ArrayList;
/**
* @author:LiWeixiao
* @date:2022/12/30
* @description:
*/
public class Test01 {
public static void main(String[] args) {
//创建一个ArrayList集合,存入学生程成绩
//加入泛型的特定,在编译时期就会对类型进行检查
//ArrayList<Integer> al = new ArrayList<Integer>();
ArrayList<Integer> al = new ArrayList<>();
al.add(98);
al.add(18);
al.add(39);
al.add(60);
//al.add("丽丽");
//al.add(9.8);
//遍历输出
for (Object o : al) {
System.out.println(o);
}
//遍历输出
System.out.println("----------");
for (Integer i:al) {
System.out.println(i);
}
}
}
11.3.2 泛型类、泛型接口
1.实例化
package com.liweixiao.test02;
/**
* @author:LiWeixiao
* @date:2022/12/30
* @description:GenericTest就是一个普通的类
* GenericTest<E>是一个泛型类
* <>里面是一个参数类型,类型不确定,相当于一个占位
* 这个类型是一个引用数据类型,不是基本数据类型
*/
public class GenericTest<E> {
int age;
String name;
E sex;
public void a(E n){
}
public void b(E[] m){
}
}
class Test{
public static void main(String[] args) {
//GenericTest进行实例化
//(1)实例化时不指定泛型
GenericTest gt1=new GenericTest();
gt1.a("abc");
gt1.a(17);
gt1.a(9.8);
gt1.b(new String[]{"a","b","c"});
//(2)实例化时指定泛型
//推荐:指定类型,进行限制
GenericTest<String> gt2 = new GenericTest<>();
gt2.sex="男";
gt2.a("abc");
gt2.b(new String[]{"a","b","c"});
}
}
2.父类指定泛型
class SubGenericTest extends GenericTest<Integer>{
}
class Demo{
public static void main(String[] args) {
//指定父类泛型,那么子类就不需要再指定泛型了,可以直接使用
SubGenericTest sgt = new SubGenericTest();
sgt.a(19);
}
}
3.父类不指定泛型
class SubGenericTest2<E> extends GenericTest<E>{
}
class Demo2{
public static void main(String[] args) {
SubGenericTest2<String> sgt2 = new SubGenericTest2<>();
sgt2.a("abc");
sgt2.sex="女";
}
}
4.泛型可以定义多个参数类型
package com.liweixiao.test03;
import java.util.ArrayList;
/**
* @author:LiWeixiao
* @date:2022/12/30
* @description:
*/
public class TestGeneric<A,B,C> {
A age;
B name;
C sex;
public void a(A m,B n,C x){
}
//构造方法不能有泛型参数
/*public TestGeneric<A,B,C>(){
}*/
public void b(){
ArrayList<String> list1=null;
ArrayList<Integer> list2=null;
//不同泛型的引用类型不可以相互赋值
//list1=list2;
}
//静态方法不能使用泛型
/*public static int c(A a){
return 10;
}*/
}
11.3.3 泛型方法
package com.liweixiao.test04;
/**
* @author:LiWeixiao
* @date:2022/12/30
* @description:
*/
public class TestGeneric<E> {
//不是泛型方法,不能是静态方法
public void a(E e){
}
//是泛型方法,1.这个方法的泛型的参数类型要和当前的类的泛型无关
//2.泛型方法定义的时候,前面要加上<T>
//3.T的类型在调用方法的时候确定的
//4.泛型方法可以是静态方法
public static <T> void b(T t){
}
}
class Demo{
public static void main(String[] args) {
TestGeneric<String> tg = new TestGeneric<>();
tg.a("abc");
tg.b("abc");
tg.b(19);
tg.b(false);
}
}
泛型参数存在继承关系的情况
package com.liweixiao.test05;
import java.util.ArrayList;
import java.util.List;
/**
* @author:LiWeixiao
* @date:2023/1/3
* @description:
*/
public class Test {
public static void main(String[] args) {
Object obj = new Object();
String str = new String();
obj=str;//父类引用指向子类,多态的一种形式
Object[] objArr = new Object[10];
String[] strArr = new String[10];
objArr=strArr;//父类引用指向子类,多态的一种形式
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
//list1=list2;//ArrayList底层都是Object类型,是并列关系
//总结:A和B是子类父类的关系,但是G<A>和G<B>不存在继承关系的,是并列关系。
}
}
在没有通配符的时候,下面的a方法,相当于方法的重复定义,报错。
package com.liweixiao.test06;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.List;
/**
* @author:LiWeixiao
* @date:2023/1/3
* @description:
*/
public class Test {
/*public void a(List<Object> list){
}
public void a(List<String> list){
}
public void a(List<Integer> list){
}*/
public void a(List<?> list){
//1.遍历:内部遍历的时候用Object即可,不用?
for(Object a:list){
System.out.println(a);
}
//2.数据的写入操作
//list.add("abc");//出错,不能随意的添加数据
list.add(null);
//3.数据的读取操作
Object obj = list.get(0);
}
}
class T{
public static void main(String[] args) {
Test t = new Test();
t.a(new ArrayList<Integer>());
t.a(new ArrayList<String>());
t.a(new ArrayList<Object>());
}
}
引入通配符?后,G<?>就变成了G<A>和G<B>的父类。
package com.liweixiao.test06;
import java.util.ArrayList;
import java.util.List;
/**
* @author:LiWeixiao
* @date:2023/1/3
* @description:
*/
public class Demo {
public static void main(String[] args) {
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<Integer> list3 = new ArrayList<>();
List<?> list=null;//通配符
list=list1;
list=list2;
list=list3;
}
}
泛型受限:泛型的上限,泛型的下限
package com.liweixiao.test07;
import java.util.ArrayList;
import java.util.List;
/**
* @author:LiWeixiao
* @date:2023/1/3
* @description:
*/
public class Test {
public static void main(String[] args) {
//a,b,c三个集合是并列关系
List<Object> a = new ArrayList<>();
List<Person> b = new ArrayList<>();
List<Student> c = new ArrayList<>();
//开始使用泛型受限
//泛型的上限:List<? extends Person>是List<Person>的父类,是List<Person子类>的父类
List<? extends Person> list1=null;
//list1=a;//报错
list1=b;
list1=c;
//泛型的下限:List<? extends Person>是List<Person>的父类,是List<Person父类>的父类
List<? super Person> list2=null;
list2=a;
list2=b;
//list2=c;//报错
}
}
11.2.4 LinkedList实现类
【LinkedList常用方法】
package com.liweixiao.test04;
import java.util.Iterator;
import java.util.LinkedList;
/**
* @author:LiWeixiao
* @date:2023/1/3
* @description:
*/
public class Test {
public static void main(String[] args) {
/*LinkedList常用方法:
增加:add、addFirst、addLast、
offer、offerFirst、offerLast
删除:poll、pollFirst、pollLast、
remove、removeFirst、removeLast
修改:
查看:element、getFirst、getLast、indexOf、lastIndexOf、peak、peakFirst、peakLast
判断:
*/
LinkedList<String> list = new LinkedList<>();
list.add("aaa");//添加到尾部
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.add("eee");
list.add("bbb");//可以添加重复的数据
list.add("fff");
System.out.println(list.size());//7
list.addFirst("jj");//添加到头部
list.addLast("hh");//添加到尾部
list.offer("kk");//添加到尾部
list.offerFirst("pp");//添加到头部
list.offerLast("rr");//添加到尾部
System.out.println(list);
System.out.println(list.poll());//删除头部元素,并输出
System.out.println(list.pollFirst());//删除头部元素,并输出
System.out.println(list.pollLast());//删除尾部元素,并输出
System.out.println(list.remove());//删除头部元素,并输出
System.out.println(list.removeFirst());//删除头部元素,并输出
System.out.println(list.removeLast());//删除尾部元素,并输出
System.out.println(list);
LinkedList<Object> list1 = new LinkedList<>();
System.out.println(list1);//[]
System.out.println(list1.pollFirst());//null, 从jdk1.6开始,提高了代码的健壮性
//System.out.println(list1.removeFirst());//报错,从jdk1.0开始,没有头部元素
//element、getFirst、getLast、indexOf、lastIndexOf、peek、peekFirst、peekLast
System.out.println(list.element());//获取头部元素
System.out.println(list.getFirst());//获取头部元素
System.out.println(list.getLast());//获取尾部元素
System.out.println(list.indexOf("ddd"));
System.out.println(list.lastIndexOf("bbb"));
System.out.println(list.peek());//获取头部元素
System.out.println(list.peekFirst());//获取头部元素
System.out.println(list.peekLast());//获取尾部元素
//集合的遍历
//1.普通for循环
System.out.println("-------------");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//2.增强for循环
System.out.println("///////////////");
for (String str:
list) {
System.out.println(str);
}
//3.迭代器1
System.out.println("*************");
Iterator<String> it = list.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
//4.迭代器2,这种方式好,节省内存,it的声明周期小
System.out.println("............");
for(Iterator<String> it1 = list.iterator();it1.hasNext();){
System.out.println(it1.next());
}
}
}
【LinkedList底层原理】
ArrayList数据结构:物理结构,紧密结构;逻辑结构,线性表(数组)
LinkedList数据结构:物理结构,跳转结构;逻辑结构,线性表(链表),双向链表

以上图片来自网络
【模拟LinkedList源码】
package com.liweixiao.test05;
/**
* @author:LiWeixiao
* @date:2023/1/4
* @description:节点类
*/
public class Node {
//三个属性:
//上一个元素的地址
private Node pre;
//当前存入的元素
private Object obj;
//下一个元素的地址
private Node next;
public Node getPre() {
return pre;
}
public void setPre(Node pre) {
this.pre = pre;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"pre=" + pre +
", obj=" + obj +
", next=" + next +
'}';
}
}
package com.liweixiao.test05;
/**
* @author:LiWeixiao
* @date:2023/1/4
* @description:
*/
public class MyLinkedList {
//链中一定有个首节点
Node first;
//链中一定有个尾节点
Node last;
//计数器
int count=0;
//提供一个构造器
public MyLinkedList(){
}
//添加元素方法
public void add(Object o){
if(first ==null){//添加第一个节点
//将添加的元素封装为一个Node对象
Node n = new Node();
n.setPre(null);
n.setObj(o);
n.setNext(null);
//当前链中第一个节点变为n
first=n;
//当前链中最后一个节点变为n
last=n;
}else {//不是第一个节点
//将添加的元素封装为一个Node对象
Node n = new Node();
n.setPre(last);//n的上一个节点是当前链中最后一个节点last
n.setObj(o);
n.setNext(null);
//当前链中最后一个节点的下一个元素,要指向n
last.setNext(n);
//将最后一个节点变为n
last=n;
}
//链中元素数量加1
count++;
}
//得到集合中元素的数量
public int getSize(){
return count;
}
//通过下标得到元素
public Object get(int index){
//获取链表头元素
Node n=first;
//一路next得到想要的元素
for(int i=0;i<index;i++){
n=n.getNext();
}
return n.getObj();
}
}
class Test{
public static void main(String[] args) {
//创建一个MyLinkedList集合对象
MyLinkedList ml = new MyLinkedList();
ml.add("aa");
ml.add("bb");
ml.add("cc");
System.out.println(ml.getSize());
System.out.println(ml.get(1));
System.out.println(ml.get(2));
}
}
11.4 iterator()、Iterator、Iterable关系

图片来自网络
ListIterator
package com.liweixiao.test06;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
/**
* @author:LiWeixiao
* @date:2023/1/4
* @description:
*/
public class Test02 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
//报错并发修改异常ConcurrentModificationException
/*Iterator<String> it = list.iterator();
while (it.hasNext()){
if("cc".equals(it.next())){
list.add("kk");
}
}*/
ListIterator<String> it = list.listIterator();
while (it.hasNext()){
if("cc".equals(it.next())){
it.add("kk");
}
}
System.out.println(list);
//正向遍历
System.out.println(it.hasNext());//false
System.out.println(it.hasPrevious());//true
System.out.println("------------");
//逆向遍历
while (it.hasPrevious()){
System.out.println(it.previous());
}
System.out.println(it.hasNext());//true
System.out.println(it.hasPrevious());//false
}
}
11.2.5 set接口
List,不唯一,有序;Set,唯一,无序(相对List接口来说的,无序不等于随机)
11.5 hashCode、equals
11.2.6 HashSet实现类
【1】放入Integer类型数据
package com.liweixiao.test07;
import java.util.HashSet;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class TestInteger {
public static void main(String[] args) {
//创建一个HashSet
HashSet<Integer> hs = new HashSet<>();
System.out.println(hs.add(19));//true
hs.add(5);
hs.add(20);
System.out.println(hs.add(19));//false,这个19没有放入到集合中
hs.add(41);
hs.add(0);
System.out.println(hs.size());//5
System.out.println(hs);//[0, 19, 20, 5, 41],唯一、无序
}
}
【2】放入String类型数据
package com.liweixiao.test07;
import java.util.HashSet;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class TestString {
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>();
hs.add("hello");
System.out.println(hs.add("apple"));//true
hs.add("banana");
hs.add("html");
System.out.println(hs.add("apple"));//false,这个apple没有放入到集合中
hs.add("css");
System.out.println(hs.size());//5
System.out.println(hs);//[banana, apple, css, html, hello],唯一、无序
}
}
【3】放入自定义的引用类型的数据,不满足唯一无序的原理
package com.liweixiao.test07;
import java.util.Objects;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class Student {
//属性
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//方法
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
//构造器
public Student(int age, String name) {
this.age = age;
this.name = name;
}
//重写hashCode和equals
@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 && name.equals(student.name);
}
@Override
public int hashCode() {
return Objects.hash(age, name);
}
}
package com.liweixiao.test07;
import java.util.HashSet;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class TestStudent {
public static void main(String[] args) {
HashSet<Student> hs = new HashSet<>();
hs.add(new Student(19,"lili"));
hs.add(new Student(20,"lulu"));
hs.add(new Student(18,"feifei"));
hs.add(new Student(19,"lili"));
hs.add(new Student(10,"nana"));
System.out.println(hs.size());//5
System.out.println(hs);//不满足唯一无序的原理
//Student类重写hashCode和equals方法后,满足唯一无序的原理。
}
}
【4】HashSet简要原理图
底层原理:数组+链表=哈希表。
注意:如果放入HashSet中的数据,一定要重写两个方法:hashCode、equals。

图片来自网络
11.2.7 LinkedHashSet实现类
唯一有序,按照输入顺序进行输出。
在HashSet基础上,多了一个总的链表,将放入的元素串在一起,方便有序的遍历。
11.7 比较器的使用
【1】比较int类型数据
【2】比较String类型数据
【3】比较double类型数据
package com.liweixiao.test08;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class Test01 {
public static void main(String[] args) {
int i1=10;
int i2 =20;
System.out.println(i1- i2);//-10
System.out.println(((Integer) i1).compareTo((Integer) i2));//-1,返回-1/0/1三个值
String str1="A";
String str2="D";
System.out.println(str1.compareTo(str2));//-3
double d1=9.6;
double d2=8.3;
System.out.println(d1 - d2);//1.2999
System.out.println(((Double) d1).compareTo((Double) d2));//1,返回-1/0/1三个值
}
}
【4】比较自定义的数据类型
(1)内部比较器implements Comparable
package com.liweixiao.test08;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class Student implements Comparable<Student>{
//属性
private int age;
private double height;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//构造器
public Student(int age, double height, String name) {
this.age = age;
this.height = height;
this.name = name;
}
//方法
@Override
public String toString() {
return "Student{" +
"age=" + age +
", height=" + height +
", name='" + name + '\'' +
'}';
}
@Override
public int compareTo(Student o) {
//1.按照年龄比较
//return this.age-o.age;
//return this.getAge()-o.getAge();
//2.按照身高比较
//return ((Double)(this.getHeight())).compareTo((Double)(o.getHeight()));
//3.按照姓名比较
return this.getName().compareTo(o.getName());
}
}
package com.liweixiao.test08;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class Test02 {
public static void main(String[] args) {
//比较两个学生:
Student s1 = new Student(10, 160.5, "lili");
Student s2 = new Student(14, 170.5, "nana");
System.out.println(s1.compareTo(s2));
}
}
(2)外部比较器implements Comparator
class BiJiao01 implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge()-o2.getAge();
}
}
class BiJiao02 implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return ((Double)(o1.getHeight())).compareTo((Double) (o2.getHeight()));
}
}
class BiJiao03 implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());
}
}
class BiJiao04 implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
//先比较年龄;年龄相同的情况,比较身高
int i = o1.getAge() - o2.getAge();
if(i !=0){
return i;
}else {
return ((Double)(o1.getHeight())).compareTo((Double) (o2.getHeight()));
}
}
}
package com.liweixiao.test09;
import java.util.Comparator;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class Test02 {
public static void main(String[] args) {
//比较两个学生:
Student s1 = new Student(10, 160.5, "lili");
Student s2 = new Student(14, 170.5, "nana");
//获取比较器
BiJiao01 bj1 = new BiJiao01();
System.out.println(bj1.compare(s1, s2));//-4
Comparator bj2 = new BiJiao02();
System.out.println(bj2.compare(s1, s2));//-1
Comparator bj3 = new BiJiao03();
System.out.println(bj3.compare(s1, s2));//-2
Comparator bj4=new BiJiao04();
System.out.println(bj4.compare(s1, s2));
}
}
【5】内部比较器和外部比较器 比较
外部比较器好用,多态,扩展性好
11.2.8 TreeSet实现类
【1】保存Integer类型数据
package com.liweixiao.test10;
import java.util.TreeSet;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class Test01 {
public static void main(String[] args) {
//创建一个TreeSet
TreeSet<Integer> ts = new TreeSet<>();
ts.add(12);
System.out.println(ts.add(3));//true
ts.add(7);
ts.add(9);
System.out.println(ts.add(3));//false
ts.add(16);
System.out.println(ts.size());//5
System.out.println(ts);//[3, 7, 9, 12, 16],唯一,无序(没有按照输入顺序),有序(按照升序)
}
}
【2】原理,底层:二叉树(数据结构)

图片来自网络
【3】放入String类型数据,底层实现 类内部比较器
package com.liweixiao.test10;
import java.util.TreeSet;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class Test02 {
public static void main(String[] args) {
TreeSet<String> ts = new TreeSet<>();
ts.add("elili");
ts.add("blili");
ts.add("elili");
ts.add("alili");
ts.add("clili");
ts.add("flili");
ts.add("glili");
System.out.println(ts.size());
System.out.println(ts);//唯一,无序,有序
}
}
【4】放入自定义的数据类型
(1)内部比较器
@Override
public int compareTo(Student o) {
return this.getAge()-o.getAge();
}
package com.liweixiao.test10;
import java.util.TreeSet;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class Test03 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
ts.add(new Student(10,"elili"));
ts.add(new Student(8,"blili"));
ts.add(new Student(4,"alili"));
ts.add(new Student(9,"elili"));
ts.add(new Student(10,"flili"));
ts.add(new Student(1,"dlili"));
System.out.println(ts.size());
System.out.println(ts);
}
}
(2)外部比较器
class BiJiao implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());
}
}
package com.liweixiao.test10;
import java.util.Comparator;
import java.util.TreeSet;
/**
* @author:LiWeixiao
* @date:2023/1/5
* @description:
*/
public class Test03 {
public static void main(String[] args) {
//利用外部比较器,必须自己指定:
Comparator<Student> com = new BiJiao();
TreeSet<Student> ts = new TreeSet<>(com);//一旦指定外部比较器,那么就会按照外部比较器来比较
ts.add(new Student(10,"elili"));
ts.add(new Student(8,"blili"));
ts.add(new Student(4,"alili"));
ts.add(new Student(9,"elili"));
ts.add(new Student(10,"flili"));
ts.add(new Student(1,"dlili"));
System.out.println(ts.size());
System.out.println(ts);
}
}
实际开发中利用外部比较器多,因为扩展性好(多态)。
换一种写法:
Comparator<Student> com = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());
}
};
【5】TreeSet底层的二叉树的遍历,是按照升序的结果

11.2.9 Collection整体结构图

11.7 Map
11.7.1 HashMap实现类
package com.liweixiao.test11;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @author:LiWeixiao
* @date:2023/1/6
* @description:
*/
public class Test01 {
public static void main(String[] args) {
/*
增加:put
删除:clear、remove
修改:
查看:entrySet、get、keySet、size、values
判断:containsKey、containsValue、equals、isEmpty
*/
//创建一个map集合
Map<String,Integer> map = new HashMap<>();
System.out.println(map.put("lili", 101010));//null
map.put("nana",123456);
map.put("feifei",111111);
System.out.println(map.put("lili", 222222));//101010
map.put("mingming",222222);
//map.clear();//清空
//map.remove("feifei");//移除
//System.out.println(map.isEmpty());//false,判断是否为空
System.out.println(map.size());//4
System.out.println(map);//{nana=123456, lili=222222, mingming=222222, feifei=111111},唯一、无序
System.out.println(map.containsKey("nana"));//true
System.out.println(map.containsValue(222222));//true
Map<String,Integer> map2 = new HashMap<>();
map2.put("lili", 101010);//null
map2.put("nana",123456);
map2.put("feifei",111111);
map2.put("lili", 222222);//101010
map2.put("mingming",222222);
System.out.println(map == map2);//false,==比较的是地址
System.out.println(map.equals(map2));//true,equals进行了重写,比较的是集合中的值是否一致
System.out.println(map.get("nana"));//123456
Set<String> key = map.keySet();//输出集合中所有的key
System.out.println(key);//[nana, lili, mingming, feifei]
Collection<Integer> values = map.values();//输出集合中所有的value
System.out.println(values);//[123456, 222222, 222222, 111111]
Set<String> key2 = map.keySet();//根据key获得value
for (String str:key2) {
System.out.println(map.get(str));
}
System.out.println(map.entrySet());//[nana=123456, lili=222222, mingming=222222, feifei=111111]
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> e:entries) {
System.out.println(e.getKey()+"---"+e.getValue());
}
}
}
11.7.2 Hashtable实现类
HashMap从jdk1.2开始,效率高,线程不安全,key可以存入null值
Hashtable从jdk1.0开始,效率低,线程安全,key不可以存入null值
11.7.3 LindedHashMap实现类
特点:唯一,有序,按照输入顺序进行输出,在HashMap上加了一个总的链表。
11.7.4 TreeMap实现类
特点:唯一,有序(按照升序)
原理:二叉树,key遵照二叉树的特点
放入集合的key的数据对应的类型内部一定要实现比较器(内部比较器 或者 外部比较器)
【1】key的类型是String类型
package com.liweixiao.test11;
import java.util.Map;
import java.util.TreeMap;
/**
* @author:LiWeixiao
* @date:2023/1/6
* @description:
*/
public class Test02 {
public static void main(String[] args) {
Map<String, Integer> tm = new TreeMap<>();
tm.put("blili",1234);
tm.put("alili",5678);
tm.put("blili",1111);
tm.put("dlili",2222);
tm.put("clili",2222);
System.out.println(tm.size());//4
System.out.println(tm);//{alili=5678, blili=1111, clili=2222, dlili=2222}
}
}
【2】key的类型是自定义类型
(1)内部比较器
package com.liweixiao.test11;
/**
* @author:LiWeixiao
* @date:2023/1/6
* @description:
*/
public class Student implements Comparable<Student>{
//属性
private int age;
private String name;
private double height;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
//构造器
public Student(int age, String name, double height) {
this.age = age;
this.name = name;
this.height = height;
}
//方法
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", height=" + height +
'}';
}
@Override
public int compareTo(Student o) {
//return this.getAge()-o.getAge();
return this.getName().compareTo(o.getName());
}
}
package com.liweixiao.test11;
import java.util.Map;
import java.util.TreeMap;
/**
* @author:LiWeixiao
* @date:2023/1/6
* @description:
*/
public class Test03 {
public static void main(String[] args) {
Map<Student,Integer> tm = new TreeMap<>();
tm.put(new Student(19, "blili", 170.5), 1001);
tm.put(new Student(18,"blili",170.5),1002);
tm.put(new Student(19, "alili", 180.5), 1002);
tm.put(new Student(17,"dlili",140.5),1001);
tm.put(new Student(10,"clili",160.5),1003);
System.out.println(tm.size());
System.out.println(tm);
}
}
(2)外部比较器
Map<Student,Integer> tm = new TreeMap<>(new Comparator<Student>() {//匿名内部类
@Override
public int compare(Student o1, Student o2) {
return ((Double)(o1.getHeight())).compareTo((Double) (o2.getHeight()));
}
});
11.7.5 HashMap的底层原理
【1】代码展示
package com.liweixiao.test01;
import java.util.HashMap;
import java.util.Map;
/**
* @author:LiWeixiao
* @date:2023/1/6
* @description:
*/
public class Test01 {
public static void main(String[] args) {
//创建一个HashMap的对象
Map<Integer,String> hm = new HashMap<>();
System.out.println(hm.put(12, "丽丽"));
hm.put(7,"菲菲");
hm.put(19,"露露");
System.out.println(hm.put(12, "明明"));
hm.put(6,"莹莹");
System.out.println(hm.size());
System.out.println(hm);
}
}
【2】先弄清楚原理,再通过源码验证
7上8下

【3】HashMap的构造器
【4】HashMap的put方法
public class HashMap<K,V>
extends AbstractMap<K,V> //【1】继承的AbstractMap中,已经实现了Map接口
//【2】又实现了这个接口,多余,但是设计者觉得没有必要删除,就这么地了
implements Map<K,V>, Cloneable, Serializable{
//【3】后续会用到的重要属性:先粘贴过来:
static final int DEFAULT_INITIAL_CAPACITY = 16;//哈希表主数组的默认长度
//定义了一个float类型的变量,以后作为:默认的装填因子,加载因子是表示Hsah表中元素的填满的程度
//太大容易引起哈西冲突,太小容易浪费 0.75是经过大量运算后得到的最好值
//这个值其实可以自己改,但是不建议改,因为这个0.75是大量运算得到的
static final float DEFAULT_LOAD_FACTOR = 0.75f;
transient Entry<K,V>[] table;//主数组,每个元素为Entry类型
transient int size;
int threshold;//数组扩容的界限值,门槛值 16*0.75=12
final float loadFactor;//用来接收装填因子的变量
//【4】查看构造器:内部相当于:this(16,0.75f);调用了当前类中的带参构造器
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//【5】本类中带参数构造器:--》作用给一些数值进行初始化的!
public HashMap(int initialCapacity, float loadFactor) {
//【6】给capacity赋值,capacity的值一定是 大于你传进来的initialCapacity 的 最小的 2的倍数
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
//【7】给loadFactor赋值,将装填因子0.75赋值给loadFactor
this.loadFactor = loadFactor;
//【8】数组扩容的界限值,门槛值
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//【9】给table数组赋值,初始化数组长度为16
table = new Entry[capacity];
}
//【10】调用put方法:
public V put(K key, V value) {
//【11】对空值的判断
if (key == null)
return putForNullKey(value);
//【12】调用hash方法,获取哈希码
int hash = hash(key);
//【14】得到key对应在数组中的位置
int i = indexFor(hash, table.length);
//【16】如果你放入的元素,在主数组那个位置上没有值,e==null 那么下面这个循环不走
//当在同一个位置上放入元素的时候
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//哈希值一样 并且 equals相比一样
//(k = e.key) == key 如果是一个对象就不用比较equals了
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//【17】走addEntry添加这个节点的方法:
addEntry(hash, key, value, i);
return null;
}
//【13】hash方法返回这个key对应的哈希值,内部进行二次散列,为了尽量保证不同的key得到不同的哈希码!
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
//k.hashCode()函数调用的是key键值类型自带的哈希函数,
//由于不同的对象其hashCode()有可能相同,所以需对hashCode()再次哈希,以降低相同率。
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
/*
接下来的一串与运算和异或运算,称之为“扰动函数”,
扰动的核心思想在于使计算出来的值在保留原有相关特性的基础上,
增加其值的不确定性,从而降低冲突的概率。
不同的版本实现的方式不一样,但其根本思想是一致的。
往右移动的目的,就是为了将h的高位利用起来,减少哈西冲突
*/
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
//【15】返回int类型数组的坐标
static int indexFor(int h, int length) {
//其实这个算法就是取模运算:h%length,取模效率不如位运算
return h & (length-1);
}
//【18】调用addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
//【25】size的大小 大于 16*0.75=12的时候,比如你放入的是第13个,这第13个你打算放在没有元素的位置上的时候
if ((size >= threshold) && (null != table[bucketIndex])) {
//【26】主数组扩容为2倍
resize(2 * table.length);
//【30】重新调整当前元素的hash码
hash = (null != key) ? hash(key) : 0;
//【31】重新计算元素位置
bucketIndex = indexFor(hash, table.length);
}
//【19】将hash,key,value,bucketIndex位置 封装为一个Entry对象:
createEntry(hash, key, value, bucketIndex);
}
//【20】
void createEntry(int hash, K key, V value, int bucketIndex) {
//【21】获取bucketIndex位置上的元素给e
Entry<K,V> e = table[bucketIndex];
//【22】然后将hash, key, value封装为一个对象,然后将下一个元素的指向为e (链表的头插法)
//【23】将新的Entry放在table[bucketIndex]的位置上
table[bucketIndex] = new Entry<>(hash, key, value, e);
//【24】集合中加入一个元素 size+1
size++;
}
//【27】
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//【28】创建长度为newCapacity的数组
Entry[] newTable = new Entry[newCapacity];
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
//【28.5】转让方法:将老数组中的东西都重新放入新数组中
transfer(newTable, rehash);
//【29】老数组替换为新数组
table = newTable;
//【29.5】重新计算
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//【28.6】
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//【28.7】将哈希值,和新的数组容量传进去,重新计算key在新数组中的位置
int i = indexFor(e.hash, newCapacity);
//【28.8】头插法
e.next = newTable[i];//获取链表上元素给e.next
newTable[i] = e;//然后将e放在i位置
e = next;//e再指向下一个节点继续遍历
}
}
}
}
11.7.6 HashSet底层原理
HashSet底层是HashMap实现
public class HashSet<E>{
//重要属性:
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
//构造器:
public HashSet() {
map = new HashMap<>();//HashSet底层就是利用HashMap来完成的
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
}
11.7.7 TreeMap底层源码
【1】原理大致结束
共6个属性,少说了1个属性:父节点的地址。

【2】源码
public class TreeMap<K,V>{
//重要属性:
//外部比较器:
private final Comparator<? super K> comparator;
//树的根节点:
private transient Entry<K,V> root = null;
//集合中元素的数量:
private transient int size = 0;
//空构造器:
public TreeMap() {
comparator = null;//如果使用空构造器,那么底层就不使用外部比较器
}
//有参构造器:
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;//如果使用有参构造器,那么就相当于指定了外部比较器
}
public V put(K key, V value) {//k,V的类型在创建对象的时候确定了
//如果放入的是第一对元素,那么t的值为null
Entry<K,V> t = root;//在放入第二个节点的时候,root已经是根节点了
//如果放入的是第一个元素的话,走入这个if中:
if (t == null) {
//自己跟自己比
compare(key, key); // type (and possibly null) check
//根节点确定为root
root = new Entry<>(key, value, null);
//size值变为1
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
//将外部比较器赋给cpr:
Comparator<? super K> cpr = comparator;
//cpr不等于null,意味着你刚才创建对象的时候调用了有参构造器,指定了外部比较器
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);//将元素的key值做比较
//cmp返回的值就是int类型的数据:
//要是这个值《0 =0 》0
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else//cpm==0
//如果key的值一样,那么新的value替换老的value 但是key不变 因为key是唯一的
return t.setValue(value);
} while (t != null);
}
//cpr等于null,意味着你刚才创建对象的时候调用了空构造器,没有指定外部比较器,使用内部比较器
else {
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);//将元素的key值做比较
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;//size加1 操作
modCount++;
return null;
}
}
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;//1.key
V value;//2.value
Entry<K,V> left = null;//3.左子树地址
Entry<K,V> right = null;//4.右子树地址
Entry<K,V> parent;//5.父节点地址
boolean color = BLACK;//6.颜色
}
11.7.8 TreeSet底层源码
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable{
//重要属性:
private transient NavigableMap<E,Object> m;
private static final Object PRESENT = new Object();
//在调用空构造器的时候,底层创建了一个TreeMap
public TreeSet() {
this(new TreeMap<E,Object>());
}
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
}
11.8 Collections工具类
类似数组的Arrays工具类
package com.liweixiao.test12;
import java.util.ArrayList;
import java.util.Collections;
/**
* @author:LiWeixiao
* @date:2023/1/9
* @description:
*/
public class Test01 {
public static void main(String[] args) {
//Collections不支持创建对象,因为构造器私有化
//Collections cols = new Collections();//报错
//常用方法:
//addAll添加多个元素
ArrayList<String> al = new ArrayList<>();
al.add("cc");
al.add("bb");
al.add("aa");
Collections.addAll(al,"dd","ee","ff");
Collections.addAll(al,new String[]{"gg","oo","pp"});
System.out.println(al.size());
System.out.println(al);
//sort排序
Collections.sort(al);//升序
System.out.println(al);
//binarySearch二分查找法
System.out.println(Collections.binarySearch(al, "cc"));
//copy替换方法
ArrayList<String> al2 = new ArrayList<>();
Collections.addAll(al2,"tt","ss");
Collections.copy(al,al2);//把al2覆盖al
System.out.println(al);//[tt, ss, cc, dd, ee, ff, gg, oo, pp]
System.out.println(al2);//[tt, ss]
//fill填充
Collections.fill(al2,"yy");
System.out.println(al2);//[yy, yy]
}
}

浙公网安备 33010602011771号