其他集合类
父类 Map
集合类的遍历方式
TreeSet 集合
具体信息请查看 API 帮助文档
TreeMap 是 Java 中的一个集合类,它实现了 SortedMap 接口。它是基于红黑树的数据结构实现的,它能够保持其中的元素处于有序状态。
TreeMap 集合中的元素是以键值对的形式存储的,其中的键用于排序和唯一性的保证,而值则用于存储具体的数据。
TreeMap 根据键的自然顺序或者自定义的比较器来进行排序,使得在遍历 TreeMap 集合时能够有序地访问其中的元素。
TreeMap 集合的特点:
TreeMap 中的键必须是可比较的,要么实现了 Comparable 接口,要么在构造 TreeMap 时提供了自定义的比较器。
TreeMap 中的键是唯一的,不允许重复的键存在。
TreeMap 是基于红黑树实现的,因此在插入、删除和查找操作的时间复杂度均为 O(logn),具有较高的效率。
TreeMap 中的元素是有序的,可以根据键来进行排序。
TreeMap 不是线程安全的,如果需要在多线程环境下使用,需要进行额外的同步处理。
TreeMap
集合是Map
集合的子类,因此Map
集合的方法TreeMap
集合都能使用。
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() | 集合的长度,也就是集合中键值对的个数 |
与共有的 集合遍历方式 一样
TreeMap
集合和TreeSet
集合一样,底层都是红黑树,因此排序方式一样
TreeSet 集合排序方式详解
默认排序规则/自然排序
比较器排序
package text.text02;
/*TreeMap基本应用:
需求:
键:整数表示id
值:字符串表示商品名称
要求:按照id的升序排列,按照id的降序排列
*/
import java.util.Comparator;
import java.util.TreeMap;
public class text52 {
public static void main(String[] args) {
//自然排序(升序排列)
System.out.println("方法一:自然排序");
method1(); //{1001=平板, 1002=汽车, 1003=手机, 1004=飞机, 1005=电脑}
//比较器排序(降序排列)
System.out.println("方法二:比较器排序");
method2(); //{1005=电脑, 1004=飞机, 1003=手机, 1002=汽车, 1001=平板}
}
//自然排序(Java底层默认的自然排序就是按照升序排列)
public static void method1() {
//创建集合并添加数据
TreeMap<Integer, String> tm = new TreeMap<>();
tm.put(1003, "手机");
tm.put(1005, "电脑");
tm.put(1001, "平板");
tm.put(1002, "汽车");
tm.put(1004, "飞机");
System.out.println(tm);
}
//比较器排序(当Java底层默认的自然排列不能满足需求时,采用比较器排列)
public static void method2() {
//创建集合(在创建对象的时候指定比较器规则)
TreeMap<Integer, String> tm = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
//添加数据
tm.put(1003, "手机");
tm.put(1005, "电脑");
tm.put(1001, "平板");
tm.put(1002, "汽车");
tm.put(1004, "飞机");
System.out.println(tm);
}
}
package text.text02;
import java.util.*;
/*
需求:
键:学生对象
值:籍贯
要求:按照学生年龄的升序排列,年龄一样按照姓名的字母排列,同姓名同年龄视为同一个人
*/
public class text53 {
public static void main(String[] args) {
//自然排序
System.out.println("方法一:自然排序");
method1();
//比较器排序
System.out.println("方法二:比较器排序");
method2();
}
//自然排序 (Java底层默认的自然排序就是按照升序排列,自定义对象时,需要在Javabean类中实现Comparable接口并重写里面的CompareTo方法)
public static void method1() {
//创建学生对象
Student8 student1 = new Student8("zhangsan", 10);
Student8 student2 = new Student8("lisi", 10);
Student8 student3 = new Student8("wangwu", 11);
Student8 student4 = new Student8("liubei", 9);
Student8 student5 = new Student8("guanyu", 13);
Student8 student6 = new Student8("guanyu", 13);
//创建集合对象
TreeMap<Student8, String> tm = new TreeMap<>();
//添加数据
tm.put(student1, "陕西");
tm.put(student2, "湖南");
tm.put(student3, "河北");
tm.put(student4, "江苏");
tm.put(student5, "北京");
tm.put(student6, "湖南");
//遍历输出集合
Set<Map.Entry<Student8, String>> entries = tm.entrySet();
for (Map.Entry<Student8, String> entry : entries) {
Student8 key = entry.getKey();
String value = entry.getValue();
System.out.println(key + " = " + value);
}
}
//比较器排序(当Java底层默认的自然排列不能满足需求时,采用比较器排列,在创建对象时,指定比较规则)
public static void method2() {
//创建学生对象
Student8 student1 = new Student8("zhangsan", 10);
Student8 student2 = new Student8("lisi", 10);
Student8 student3 = new Student8("wangwu", 11);
Student8 student4 = new Student8("liubei", 9);
Student8 student5 = new Student8("guanyu", 13);
Student8 student6 = new Student8("guanyu", 13);
//创建集合对象
TreeMap<Student8, String> tm = new TreeMap<>(new Comparator<Student8>() {
@Override
//按照学生年龄的升序排列,年龄一样按照姓名的字母排列,同姓名同年龄视为同一个人
public int compare(Student8 o1, Student8 o2) {
//o1:表示当前要添加的元素
//o2:表示已经在红黑树存在的元素
//返回值:
//负数:表示当前要添加的数据是小的,存左边
//正数:表示当前要添加的数据是大的,存右边
//0:表示当前要添加的元素已经存在,舍弃
int i = o1.getAge() - o2.getAge();
i = i == 0 ? o1.getName().compareTo(o2.getName()) : i;
return i;
}
});
//添加数据
tm.put(student1, "陕西");
tm.put(student2, "湖南");
tm.put(student3, "河北");
tm.put(student4, "江苏");
tm.put(student5, "北京");
tm.put(student6, "湖南");
//遍历输出集合
Set<Map.Entry<Student8, String>> entries = tm.entrySet();
for (Map.Entry<Student8, String> entry : entries) {
Student8 key = entry.getKey();
String value = entry.getValue();
System.out.println(key + " = " + value);
}
}
}
//学生对象
class Student8 implements Comparable<Student8> {
private String name;
private int age;
public Student8() {
}
public Student8(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
*
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
*
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
*
* @param 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;
Student8 student8 = (Student8) o;
return age == student8.age && Objects.equals(name, student8.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String toString() {
return "Student8{name = " + name + ", age = " + age + "}";
}
@Override
//按照学生年龄的升序排列,年龄一样按照姓名的字母排列,同姓名同年龄视为同一个人
public int compareTo(Student8 o) {
//this:表示当前要添加的元素
//o:表示已经在红黑树中的元素
//返回值:
//负数:表示当前要添加的数据是小的,存左边
//正数:表示当前要添加的数据是大的,存右边
//0:表示当前要添加的元素已经存在,舍弃
int i = this.getAge() - o.getAge();
i = i == 0 ? this.getName().compareTo(o.getName()) : i;
return i;
}
}
package text.text02;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.TreeMap;
/*
统计个数:
需求:字符串“aababcabcdabcde”,请统计字符串中每一个字符出现的次数,并按照如下格式输出。
输出结果:a(5)b(4)c(3)d(2)e(1)
新的统计思想:利用Map集合进行统计 (键:表示要统计的内容;值:表示次数)
如果题目中没有要求对结果进行排序,默认使用HashMap
如果题目中要求对结果进行排序,则使用TreeMap
*/
public class text54 {
public static void main(String[] args) {
//创建数组存储每一个字符
char[] arr = {'a', 'b', 'c', 'd', 'e'};
//定义一个变量用于记录字符串
String str = "aababcabcdabcde";
//创建集合
TreeMap<Character, Integer> tm = new TreeMap<>();
for (int i = 0; i < str.length(); i++) {
//如果双列集合中存在该字符,则获取双列集合中该字符的次数并将次数+1
if (tm.containsKey(str.charAt(i))) {
//获取双列集合中该字符的次数
Integer value = tm.get(str.charAt(i));
//将次数+1
value++;
//用新次数覆盖原次数
tm.put(str.charAt(i), value);
}
//如果双列集合中存在该字符,则添加该字符并将次数改为1
else {
tm.put(str.charAt(i), 1);
}
}
//遍历集合
//1.利用StringBuilder修改输出格式
//创建StringBuilder对象
StringBuilder sb = new StringBuilder();
Set<Map.Entry<Character, Integer>> entries = tm.entrySet();
for (Map.Entry<Character, Integer> entry : entries) {
//获取集合里的键
Character key = entry.getKey();
//获取集合里的值
Integer value = entry.getValue();
//将数据按照需求格式添加进StringBuilder集合
sb.append(key).append("(").append(value).append(")");
}
System.out.println("1.利用StringBuilder修改输出格式:");
System.out.println(sb); //a(5)b(4)c(3)d(2)e(1)
//2.利用StringJoiner修改输出格式
//创建StringJoiner对象
StringJoiner sj = new StringJoiner("", "", "");
Set<Character> set = tm.keySet();
for (Character key : set) {
Integer value = tm.get(key);
//将数据按照需求格式添加进StringJoiner集合
sj.add(key + "").add("(").add(value + "").add(")");//+" "是为了将key和value转换成字符串
}
System.out.println("2.利用StringJoiner修改输出格式:");
System.out.println(sj); //a(5)b(4)c(3)d(2)e(1)
}
}
键的唯一性:TreeMap 中的键是唯一的,不允许重复的键存在。如果尝试插入一个已经存在的键,新的值将会覆盖旧的值。如果需要存储允许重复键的情况,可以考虑使用其他集合类如 ArrayList 或者 HashMap。
键的可比较性:TreeMap 要求集合中的键必须是可比较的,要么实现了 Comparable 接口,要么在构造 TreeMap 时提供了自定义的比较器。如果键没有实现 Comparable 接口,并且没有提供自定义的比较器,则在插入元素或者进行比较操作时会抛出 ClassCastException 异常。
线程安全性:TreeMap 不是线程安全的,如果需要在多线程环境下使用 TreeMap,需要进行额外的同步处理。可以考虑使用 Collections 类的 synchronizedSortedMap 方法包装 TreeMap,或者使用并发集合类如 ConcurrentHashMap。
迭代顺序:TreeMap 中的元素是有序的,可以根据键来进行排序。通过 iterator 或者 forEach 遍历 TreeMap 时,元素会按照键的顺序以升序进行遍历。
性能开销:由于 TreeMap 的底层是红黑树,插入、删除和查找操作的时间复杂度均为 O(logn),相比于其他集合类可能会有较高的性能开销。如果对性能有较高要求,可以考虑使用其他集合类。
TreeMap中每一个节点的内部属性
K key; //键
V value; //值
Entry<K,V> left; //左子节点
Entry<K,V> right; //右子节点
Entry<K,V> parent; //父节点
boolean color; //节点的颜色
TreeMap类中中要知道的一些成员变量
public class TreeMap<K,V>{
//比较器对象
private final Comparator<? super K> comparator;
//根节点
private transient Entry<K,V> root;
//集合的长度(节点的个数)
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) {
return put(key, value, true);
}
//参数一:键
//参数二:值
//参数三:当键重复的时候,是否需要覆盖值
// true:覆盖
// false:不覆盖
private V put(K key, V value, boolean replaceOld) {
//获取根节点的地址值,赋值给局部变量t
Entry<K,V> t = root;
//判断根节点是否为null
//如果为null,表示当前是第一次添加,会把当前要添加的元素,当做根节点
//如果不为null,表示当前不是第一次添加,跳过这个判断继续执行下面的代码
if (t == null) {
//方法的底层,会创建一个Entry对象,把他当做根节点
addEntryToEmptyMap(key, value);
//表示此时没有覆盖任何的元素
return null;
}
//表示两个元素的键比较之后的结果
int cmp;
//表示当前要添加节点的父节点
Entry<K,V> parent;
//表示当前的比较规则
//如果我们是采取默认的自然排序,那么此时comparator记录的是null,cpr记录的也是null
//如果我们是采取比较器排序方式,那么此时comparator记录的是就是比较器
Comparator<? super K> cpr = comparator;
//表示判断当前是否有比较器对象
//如果传递了比较器对象,就执行if里面的代码,此时以比较器的规则为准
//如果没有传递比较器对象,就执行else里面的代码,此时以自然排序的规则为准
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else {
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
} else {
//把键进行强转,强转成Comparable类型的
//要求:键必须要实现Comparable接口,如果没有实现这个接口
//此时在强转的时候,就会报错。
Comparable<? super K> k = (Comparable<? super K>) key;
do {
//把根节点当做当前节点的父节点
parent = t;
//调用compareTo方法,比较根节点和当前要添加节点的大小关系
cmp = k.compareTo(t.key);
if (cmp < 0)
//如果比较的结果为负数
//那么继续到根节点的左边去找
t = t.left;
else if (cmp > 0)
//如果比较的结果为正数
//那么继续到根节点的右边去找
t = t.right;
else {
//如果比较的结果为0,会覆盖
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
}
//就会把当前节点按照指定的规则进行添加
addEntry(key, value, parent, cmp < 0);
return null;
}
private void addEntry(K key, V value, Entry<K, V> parent, boolean addToLeft) {
Entry<K,V> e = new Entry<>(key, value, parent);
if (addToLeft)
parent.left = e;
else
parent.right = e;
//添加完毕之后,需要按照红黑树的规则进行调整
fixAfterInsertion(e);
size++;
modCount++;
}
private void fixAfterInsertion(Entry<K,V> x) {
//因为红黑树的节点默认就是红色的
x.color = RED;
//按照红黑规则进行调整
//parentOf:获取x的父节点
//parentOf(parentOf(x)):获取x的爷爷节点
//leftOf:获取左子节点
while (x != null && x != root && x.parent.color == RED) {
//判断当前节点的父节点是爷爷节点的左子节点还是右子节点
//目的:为了获取当前节点的叔叔节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//表示当前节点的父节点是爷爷节点的左子节点
//那么下面就可以用rightOf获取到当前节点的叔叔节点
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
//叔叔节点为红色的处理方案
//把父节点设置为黑色
setColor(parentOf(x), BLACK);
//把叔叔节点设置为黑色
setColor(y, BLACK);
//把爷爷节点设置为红色
setColor(parentOf(parentOf(x)), RED);
//把爷爷节点设置为当前节点
x = parentOf(parentOf(x));
} else {
//叔叔节点为黑色的处理方案
//表示判断当前节点是否为父节点的右子节点
if (x == rightOf(parentOf(x))) {
//表示当前节点是父节点的右子节点
x = parentOf(x);
//左旋
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
//表示当前节点的父节点是爷爷节点的右子节点
//那么下面就可以用leftOf获取到当前节点的叔叔节点
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
//把根节点设置为黑色
root.color = BLACK;
}
课堂思考问题:
此时是不需要重写的。
不需要的。
因为在HashMap的底层,默认是利用哈希值的大小关系来创建红黑树的
如果是最坏情况,添加了8个元素,这8个元素形成了链表,此时TreeMap的效率要更高
但是这种情况出现的几率非常的少。
一般而言,还是HashMap的效率要更高。
此时putIfAbsent本身不重要。
传递一个思想:
代码中的逻辑都有两面性,如果我们只知道了其中的A面,而且代码中还发现了有变量可以控制两面性的发生。
那么该逻辑一定会有B面。
习惯:
boolean类型的变量控制,一般只有AB两面,因为boolean只有两个值
int类型的变量控制,一般至少有三面,因为int可以取多个值。
默认:HashMap(效率最高)
如果要保证存取有序:LinkedHashMap
如果要进行排序:TreeMap