目录
一、红黑树
1.1 红黑树概述
红黑树的特点 红黑树又称为平衡二叉B树 1.每一个节点可以是红或者黑 2.红黑树不是高度平衡的,它的平衡是通过"自己的红黑规则"进行实现的
红黑树的红黑规则有哪些 1. 每一个节点或是红色的,或者是黑色的 2. 根节点必须是黑色 3. 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的 4. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连 的情况) 5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
红黑树添加节点 红黑树添加节点的默认颜色为红色,效率高
红黑树添加节点的规则
红黑树添加节点后如何保持红黑规则
1.2 案例应用
案例需求 - 用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成绩,英语成绩),并遍历该集合 - 要求: 按照总分从高到低出现,如果语文成绩相同,看英语成绩,英语相同看数学,,......数学相同 最后看姓名,姓名相同则不存
public class Demo {
public static void main(String[] args) {
//创建对象,添加属性
TreeSet<Student> set = new TreeSet<>();
set.add(new Student("zhangsan", 80, 70, 60));
set.add(new Student("zhangsan", 80, 70, 50));
set.add(new Student("lisi", 80, 60, 60));
set.add(new Student("wangwu", 60, 70, 60));
//遍历打印
Iterator<Student> it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
//控制台打印如下
//Student{name='wangwu', chinese=60, math=70, englishi=60,sum=190}
//Student{name='lisi', chinese=80, math=60, englishi=60,sum=200}
//Student{name='zhangsan', chinese=80, math=70, englishi=50,sum=200}
//Student{name='zhangsan', chinese=80, math=70, englishi=60,sum=210}
}
}
class Student implements Comparable<Student> {
private String name;
private int chinese;
private int math;
private int englishi;
public Student() {
}
public Student(String name, int chinese, int math, int englishi) {
this.name = name;
this.chinese = chinese;
this.math = math;
this.englishi = englishi;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChinese() {
return chinese;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getEnglishi() {
return englishi;
}
public void setEnglishi(int englishi) {
this.englishi = englishi;
}
public int getSum() {
return chinese + englishi + math;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Student{");
sb.append("name='").append(name).append('\'');
sb.append(", chinese=").append(chinese);
sb.append(", math=").append(math);
sb.append(", englishi=").append(englishi);
sb.append(",sum=").append(getSum());
sb.append('}');
return sb.toString();
}
//重写Comparable中的compareTo方法
@Override
public int compareTo(Student o) {
// 主要条件: 按照总分进行排序
int result = this.getSum() - o.getSum();
// 次要条件: 如果总分一样,就按照语文成绩排序
result = result == 0 ? this.getChinese() - o.getChinese() : result;
// 如果语文成绩也一样,就按照数学成绩排序
result = result == 0 ? this.getMath() - o.getMath() : result;
//如果数学成绩也一样,就按照英语成绩排序
result = result == 0 ? this.getEnglishi() - o.getEnglishi() : result;
// 如果总分一样,各科成绩也都一样,就按照姓名排序
return result == 0 ? this.name.compareTo(o.name) : result;
}
}
二、HashSet集合
2.1 HashSet集合的概述和特点
1.底层数据结构是哈希表 2.存取无序 3.不可以存储重复元素 4.没有索引,不能使用普通for循环遍历
2.2 HashSet集合的基本使用
public class Demo {
public static void main(String[] args) {
HashSet<String>set = new HashSet<>();
set.add("asd");
set.add("asd");
set.add("asd");
set.add("sdfad");
set.add("sdd");
System.out.println(set);//[sdd, asd, sdfad]
//循环遍历打印集合
for (String s : set) {
System.out.println(s);
//控制台打印如下
//sdd
//asd
//sdfad
}
}
}
2.3 哈希值
1.哈希值简介 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值 2.如何获取哈希值 Object类中的public int hashCode():返回对象的哈希码值 3.哈希值的特点 - 同一个对象多次调用hashCode()方法返回的哈希值是相同的 - 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
HashSet集合存储自定义类型元素,要想实现元素的唯一,要求必须重写hashCode方法和equals方法
public class Demo01 {
public static void main(String[] args) {
Student s1 = new Student("zhangsan",23);
Student s2 = new Student("lisi",23);
Student s3 = new Student("zhangsan",23);
System.out.println(s1);
//如果不重写hashCode方法,则会根据地址值打印哈希值
//如果重写hashCode方法,则会根据属性值打印哈希值
//同一个对象多次调用hashCode()方法返回的哈希值是相同的,因为地址值和属性值都相同
System.out.println(s1.hashCode());//根据地址值985922955,根据属性值-1461068253
System.out.println(s1.hashCode());//根据地址值985922955,根据属性值-1461068253
System.out.println(s2.hashCode());//根据地址值1435804085,根据属性值102982116
System.out.println(s3.hashCode());//根据地址值1784662007,根据属性值-1461068253
}
}
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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student1 = (Student) o;
if (age != student1.age) return false;
return name != null ? name.equals(student1.name) : student1.name == null;
}
//重写hashCode方法
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Student{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
}
2.4 哈希表结构
JDK8之前, 不包含JDK8, 哈希表底层采用"数组+链表"实现 JDK8之后, 哈希表底层进行优化, 采用"数组+链表+红黑"树实现
2.4.1 哈希表JDK7
JDK7底层原理解析 -> 数组+链表 1. 创建默认长度为16, 默认加载因子为0.75的数组 ,数组名为table (数组存了16*0.75=12个元素时, 数组扩容为原先的两倍) 2. 根据要存入元素的哈希值, 和数组的长度计算出应存入的位置 3. 判断当前位置是否为null, 如果是null直接存入 4. 如果不是null, 表示有元素, 会调用euqals方法比较属性 5. 如果属性也一样则不存, 如果不一样则存入数组, 老元素挂在新元素下形成"链表结构"
2.4.2 哈希表JDK8
JDK8底层优化 -> 数组+链表+红黑树 问题: 在JDK7时, 如果同一个位置存入多个元素, 那么会形成链表结构 下一个要存入该位置的元素, 需要一个一个比较, 如果该链表过长, 效率非常低! 改进: 在JDK8时, 如果某条链表长度达到8时, 就会自动将链表转为红黑树 (目的是为了提高效率)
案例
//在HashSet集合中添加Student对象
public class Demo {
public static void main(String[] args) {
//创建HashSet对象,将属性值添加进集合对象
HashSet<Student> set = new HashSet<>();
set.add(new Student("zhangsan", 23));
set.add(new Student("lisi", 23));
set.add(new Student("zhangsan", 23));
System.out.println(set);
//控制台打印
/*
[Student{name='lisi', age=23}, Student{name='zhangsan', age=23}]
*/
}
}
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() {
final StringBuilder sb = new StringBuilder("Student{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
//重写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;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
2.5 Set接口小结
Set接口: 无序,无索引,不能重复 HashSet实现类: 底层哈希表,如果存储自定义类,该类要重写hashCode和equals方法 TreeSet实现类: 底层红黑树,可以排序,默认使用自然排序,也可以指定规则使用比较器排序
三、Map集合
3.1 Map集合概述和特点
Map集合概述 interface Map<K,V>; K代表键的数据类型, V代表值的数据类型 Map集合的特点 1.双列集合,一个键对应一个值 2.键不可以重复,值可以重复 3.键+值这个整体称为"键值对"或者"键值对对象", 在Java中也称"Entry对象" 4.创建Map集合的对象, 通过多态的方式, 使用其实现类对象HashMap
3.2 Map集合的方法
3.2.1 Map集合的基本方法
方法名 | 说明 |
---|---|
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
public class Demo {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
//V put(K key,V value) 添加元素
map.put("123", "456");
map.put("111", "555");
map.put("123", "369");
map.put("333", "666");
map.put("222", "666");
System.out.println(map);//{111=555, 123=369, 222=666, 333=666}
//V remove(Object key) 根据键删除键值对元素
String value = map.remove("111");
System.out.println(value);//555
//void clear() 移除所有的键值对元素
//map.clear();
//boolean containsKey(Object key) 判断集合是否包含指定的键
boolean b = map.containsKey("222");
System.out.println(b);//true
//boolean containsValue(Object value) 判断集合是否包含指定的值
boolean b1 = map.containsValue("456");
System.out.println(b1);//true
//boolean isEmpty() 判断集合是否为空
boolean empty = map.isEmpty();
System.out.println(empty);//false
//int size() 集合的长度,也就是集合中键值对的个数
int size = map.size();
System.out.println(size);//3
System.out.println(map);
}
}
3.2.2 Map集合的获取方法
方法名 | 说明 |
---|---|
V get(Object key) | 根据键获取值 |
Set<K> keySet() | 获取所有键的集合 |
Collection<V> values() | 获取所有值的集合 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
public class Demo {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("123", "456");
map.put("222", "999");
map.put("333", "888");
map.put("444", "777");
// V get(Object key) 根据键获取值
System.out.println(map.get("444"));//777
// Set keySet() 获取所有键的集合
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key);
/*
控制台打印
123
222
333
444*/
}
// Collection values() 获取所有值的集合
Collection<String> values = map.values();
for (String value : values) {
System.out.println(value);
/*
控制台打印
456
999
888
777*/
}
// Set> entrySet() 获取所有键值对对象的集合
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey()+"--"+entry.getValue());
/*
控制台打印
123--456
222--999
333--888
444--777*/
}
}
}
3.3 Map的遍历方式
public class Demo {
public static void main(String[] args) {
//创建集合对象
Map<String,String>map = new HashMap<>();
//V put(K key, V value) 将指定的值与该映射中的指定键相关联
map.put("001","张三");
map.put("002","李四");
map.put("003","王五");
map.put("004","张三");
map.put("005","张三");
map.put("006","张三");
map.put("006","hhh");
//第一种遍历方式, 获取所有键值对对象的集合
Set<Map.Entry<String, String>> entries = map.entrySet();
//遍历键值对对象的集合,得到每一个键值对对象
for (Map.Entry<String, String> entry : entries) {
//根据键值对对象获取键和值
System.out.println(entry.getKey()+"----"+entry.getValue());
}
//控制台打印
/* 001----张三
002----李四
003----王五
004----张三
005----张三
006----hhh*/
//第二种遍历方式,获取所有键的集合,用keySet()方法实现
Set<String>keys = map.keySet();
//遍历键的集合,获取到每一个键,用增强for实现
for (String key : keys) {
//根据键去找值,用get(Object key)方法实现
String value = map.get(key);
System.out.println(key+"----"+value);
}
//第三种遍历方式
map.forEach(
(k,v)->{
System.out.println(k+"===="+v);
}
);
//第四种遍历方式
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String s, String s2) {
System.out.println(s+"==="+s2);
}
});
}
}
四、HashMap集合
4.1 HashMap集合的概述和特点
1. HashMap是是Map接口的实现类 2. HashMap跟HashSet一样, 底层是哈希表结构的 3. 依赖HashCode方法和equals方法保证"键"的唯一 4. 如果"键"要存储的是自定义类的对象, 那么要重写该类的HashCode和equals方法
4.2 HashMap案例
案例需求 创建一个HashMap集合,键是学生对象(Student),值是居住地 (String),存储多个元素,并遍历。 要求保证键的唯一性:如果学生对象的成员变量值相同,我们就认为是同一个对象.
public class Demo {
public static void main(String[] args) {
Map<Student, String> map = new HashMap<>();
map.put(new Student("zhangsan", 23), "美国");
map.put(new Student("zhangsan", 23), "英国");
map.put(new Student("wangmazi", 23), "中国");
map.put(new Student("lisa", 24), "中国");
//第一种遍历方式, 获取键的集合, 再根据键依次获取值
Set<Student> keys = map.keySet();
for (Student key : keys) {
String value = map.get(key);
System.out.println(key + "---" + value);
}
//第二种遍历方式, 获取键值对集合, 再分别获取键和值
Set<Map.Entry<Student, String>> entries = map.entrySet();
for (Map.Entry<Student, String> entry : entries) {
System.out.println(entry.getKey() + "---" + entry.getValue());
}
//第三种遍历方式, foreach接收实现类对象, 可以使用lambda优化
map.forEach(
(k, v) -> {
System.out.println(k + "-----" + v);
}
);
//第四种遍历方式foreach
//在foreach底层相当于遍历了map集合, 然后将获取的键和值交给抽象方法accept
//所以需要一个接口BiConsumer的实现类, 重写accept方法, 完成打印键和值即可
map.forEach(new BiConsumer<Student, String>() {
@Override
public void accept(Student student, String s) {
System.out.println(student + "-----" + s);
}
});
}
}
class Student implements Comparable<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;
}
//键是自定义学生对象(Student), 所以要重写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;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Student{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
@Override
public int compareTo(Student o) {
int result = this.getAge()-o.getAge();
return result==0? this.name.compareTo(o.name):result;
}
}
五、TreeMap集合
5.1 TreeMap集合的概述和特点
1. TreeMap是Map接口的实现类 2. TreeMap跟TreeSet一样, 底层是红黑树结构的 (再看一遍分析即可) 3. 依赖自然排序, 或者比较器排序, 对"键"进行排序 4. 如果"键"要存储的是自定义类的对象, 那么要实现Comparable接口或者在创建TreeMap对象时给出比较器排序规则
5.2 TreeMap案例
案例需求 创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String),学生属性姓名和年龄,按照年龄进行排序并遍历. 要求按照学生的年龄进行排序,如果年龄相同则按照姓名进行排序
public class Demo {
public static void main(String[] args) {
//创建TreeMap集合,键是学生对象(Student),值是籍贯(String)
//使用比较器排序
//重写Comparator的compare方法
TreeMap<Student, String> map = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int result = o1.getAge() - o2.getAge();
return result == 0 ? o1.getName().compareTo(o2.getName()) : result;
}
});
//在map集合中添加对象的属性和他们的籍贯
map.put(new Student("zhangsan", 23), "美国");
map.put(new Student("lisa", 22), "英国");
map.put(new Student("wangmazi", 23), "日本");
map.put(new Student("wangmazi", 22), "中国");
map.put(new Student("liyang", 25), "韩国");
//遍历集合
//第一种遍历方式
map.forEach(
(k, v) -> {
System.out.println(k + "-----" + v);
}
);
//控制台打印
/*Student{name='lisa', age=22}-----英国
Student{name='wangmazi', age=22}-----中国
Student{name='wangmazi', age=23}-----日本
Student{name='zhangsan', age=23}-----美国
Student{name='liyang', age=25}-----韩国*/
//第二种遍历方式
map.forEach(new BiConsumer<Student, String>() {
@Override
public void accept(Student student2, String s) {
System.out.println(student2 + "==" + s);
}
});
//第三种遍历方式
Set<Map.Entry<Student, String>> entries = map.entrySet();
for (Map.Entry<Student, String> entry : entries) {
System.out.println(entry.getKey() + "===" + entry.getValue());
}
//第四种遍历方式
Set<Student> kes = map.keySet();
for (Student key : kes) {
String value = map.get(key);
System.out.println(key+"---"+value);
}
}
}
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() {
final StringBuilder sb = new StringBuilder("Student{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
}