来,进来的小伙伴们,我们认识一下。
我是俗世游子,在外流浪多年的Java程序猿
前面我们已经介绍了HashMap,今天我们来看看Map的另外一个子类:TreeMap
首先在介绍TreeMap之前,我们先了解一些前置知识,往下看
在了解排序方式之前,我们先来聊一聊什么是:有序,无序,排序
有序
保证插入的顺序和在容器中存储的顺序是一致的,典型代表:
无序
插入的顺序和在容器中存储的顺序不一致的,典型代表:
排序
基于某种规则在迭代的时候输出符合规则的元素顺序, 比如:
那么我们来看具体的排序方式
那么,现在有一种需求,就是我们按照一定顺序将集合中的元素进行输出,那么我们该怎么做呢?基于这种方式,Java为我们提供了两种实现方式:
实现该接口需要一个实体对象,然后重写其compareTo()
,我们来看例子:
// 定义一个Student对象
class Student implements Comparable<Student> {
public int id;
public String name;
public int age;
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
/**
* 对比方法
*/
@Override
public int compareTo(Student o) {
// 按照年龄从小到大的排序方式
return age - o.age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name=‘" + name + ‘\‘‘ +
", age=" + age +
‘}‘;
}
}
// 小案例
ArrayList<Student> students = new ArrayList<Student>(6) {{
add(new Student(1, "张三", 20));
add(new Student(2, "里斯", 18));
add(new Student(3, "王五", 38));
add(new Student(4, "赵柳", 10));
add(new Student(5, "天气", 77));
}};
// 排序前的输出
System.out.println("排序前的输出:");
System.out.println(students);
System.out.println("================");
// 排序操作
Collections.sort(students);
System.out.println("排序后的输出:");
System.out.println(students);
/**
排序前的输出
[Student{id=1, name=‘张三‘, age=20}, Student{id=2, name=‘里斯‘, age=18}, Student{id=3, name=‘王五‘, age=38}, Student{id=4, name=‘赵柳‘, age=10}, Student{id=5, name=‘天气‘, age=77}]
================
[Student{id=4, name=‘赵柳‘, age=10}, Student{id=2, name=‘里斯‘, age=18}, Student{id=1, name=‘张三‘, age=20}, Student{id=3, name=‘王五‘, age=38}, Student{id=5, name=‘天气‘, age=77}]
**/
可以看到我们已经实现了按照年龄从小到大的顺序进行排序的
这里需要注意一点,在compareTo()
中,是传入的对象和当前对象进行对比:
这种方式是通过外部类的方式进行编写,还是上面的代码,我们改一些地方:
Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 按照ID降序排序
return (int) (o2.id - o1.id);
}
});
/**
排序前的输出:
[Student{id=1, name=‘张三‘, age=20}, Student{id=2, name=‘里斯‘, age=20}, Student{id=3, name=‘王五‘, age=38}, Student{id=4, name=‘赵柳‘, age=10}, Student{id=5, name=‘天气‘, age=77}]
================
排序后的输出:
[Student{id=5, name=‘天气‘, age=77}, Student{id=4, name=‘赵柳‘, age=10}, Student{id=3, name=‘王五‘, age=38}, Student{id=2, name=‘里斯‘, age=20}, Student{id=1, name=‘张三‘, age=20}]
**/
查看,已经实现了需求
在compare()
中返回值的对比和第一种方式是一样的
大家按照实际的需求选择合理的排序方式吧
树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
摘抄自:百度百科:Tree
二叉树是树形结构中的一种重要类型,是我们在数据结构中最常用的树结构之一,每个节点下最多只有两个子节点
顾名思义,二叉搜索树是以二叉树来组织的,对比二叉树,拥有以下特性:
二叉搜索树的插入过程
也成AVL树,是基于二叉搜索树的一种扩展,也就是说拥有二叉搜索树的全部特性。二叉搜索树存在缺点:
AVL树针对这一情况进行了改进:
基于平衡树的一种演进,也存在旋转操作保持二叉树的平衡,同时在此基础上添加变色操作,拥有如下特性:
这里没有讲解的很详细,简单的说一下有个概念
下面我们来看一下TreeMap:
之前我说的有些问题:我们遇到一个类,它最重要的是类中的注释,我们先来看TreeMap的注释是如何介绍TreeMap的:
SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
下面我们来具体看看我们如何使用TreeMap的
TreeMap<String, String> treeMap = new TreeMap<>();
new TreeMap<String, Long>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return 0;
}
});
public TreeMap() {
comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
第二个构造方法传入一个比较器,这个我们在 排序方式 中就已经说到,也明白了返回的含义
但是这里要注意一点:如果我们没有传入 比较器,默认为null,那么我们需要明白:
在了解该方法之前,我们先来了解一个类:
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
}
我们已经知道,TreeMap底层是采用红黑树的结构来存储数据,那么对应到代码中的实现就是上面的样子。
下面我们来看具体是如何添加元素的
public V put(K key, V value) {
Entry<K,V> t = root;
// 根节点
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
// 比较器比较 得到当前节点应该归属的父节点
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
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
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.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++;
modCount++;
return null;
}
总结一下,可以分为四步来进行操作:
判断如果当前根节点为null,那么当前插入的第一个元素就为根节点元素
前面3点都很简单,无非就是通过do..while
循环通过排序器进行对比,这里有一点,也就是在构造方法里我提到的一点:
class A {
public Long id;
}
TreeMap<A, String> map = new TreeMap<>();
map.put(new A(), "11");
map.put(new A(), "11");
System.out.println(map);
// java.lang.ClassCastException: zopx.top.study.jav.maps.A cannot be cast to java.lang.Comparable
在采用默认构造方法的时候,这样的方式出现错误:类型转换异常,这也就是为什么TreeMap:Key必须要实现Comparable
的原因
下来我们重点看看节点变色和旋转操作
private void fixAfterInsertion(Entry<K,V> x) {
// 将当前节点标记为红色
x.color = RED;
// 当插入元素后出现不平衡,则进行调整
while (x != null && x != root && x.parent.color == RED) {
// 判断是否是左边节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
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 {
// 否则就是右边节点
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;
}
下面我通过画图来进行代码分析吧,这样更容易理解:
这是一个最简单的例子,节点也非常少,大家可以自己按照上面的方式过一下代码,理解下代码的逻辑
右旋操作
private void rotateRight(Entry<K,V> p) {
if (p != null) {
Entry<K,V> l = p.left;
p.left = l.right;
if (l.right != null) l.right.parent = p;
l.parent = p.parent;
if (p.parent == null)
root = l;
else if (p.parent.right == p)
p.parent.right = l;
else p.parent.left = l;
l.right = p;
p.parent = l;
}
}
同样,在红黑树中还包含左旋的操作,大家可以自己看下源代码:
rotateLeft()
,和右旋很类似最好是能够边分析源代码,边通过画图的方式加深理解
上面也就是TreeMap基于红黑树的实现方式,大家可以结合上面介绍的红黑树的特性好好理解下
remove(Object key)
方法和put()
方法相差不多,大家可以自己看看源码
下面简单来说一下get()
方法:
Entry<K,V> p = getEntry(key);
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
// 默认构造器
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
// 不断对比,如果==0,那么就是当前需要的Entry
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
// 自定义排序方式
final Entry<K,V> getEntryUsingComparator(Object key) {
@SuppressWarnings("unchecked")
K k = (K) key;
Comparator<? super K> cpr = comparator;
if (cpr != null) {
Entry<K,V> p = root;
while (p != null) {
// 不断对比,如果==0,那么就是当前需要的Entry
int cmp = cpr.compare(k, p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
}
return null;
}
该方法还是比较简单的,也就是在while()
循环中通过比较器进行对比
更多关于TreeMap使用方法推荐查看其文档:
原文:https://blog.51cto.com/14948012/2551951