示例
public class LinkedHashMapDemo {
public static void main(String[] args) {
Map<String, Object> data = new LinkedHashMap<>();
data.put("A", "Hello");
data.put("B","World");
data.put("C","Kevin");
data.put("D","Job");
System.out.println(data);
}
}
结果:{A=Hello, B=World, C=Kevin, D=Job}
public class TreeMapDemo {
public static void main(String[] args) {
Map<String, Object> data = new TreeMap<>();
data.put("A", "Hello");
data.put("C","Kevin");
data.put("D","Job");
data.put("B","World");
data.put("0","Tim");
System.out.println(data);
}
}
结果:{0=Tim, A=Hello, B=World, C=Kevin, D=Job}
LinkedHashMap原理解析
推荐这篇文章写得相当深度,非常值得细读一下 LinkedHashMap原理解析
LinkedHashMap继承自HashMap,只是重写了部分方法以及内部数据结构,如下:
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
显而易见,before, after就是维护双向链表的。那么,是如何维护前后驱节点呢?
put方法的时候是调用HashMap的,其中 newNode会调用到LinkedHashMap,
下面粘贴出LinkedHashMap重写的方法:
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
简单示例过程如下:
首次put: p1 = entry<1,2> last = null tail = p1 head = p1
第二次put: p2 = entry<2,3> last = tail = p1 tail = p2 p2.before = p1 p1.after = p2
TreeMap原理解析
TreeMap是基于红黑树实现的有序集合,默认是按照自然排序。
红黑树不清楚的可以看一下这篇文章,讲的也很详细:红黑树详解
面试旧敌之红黑树
红黑树实现自平衡的特性:
- 节点是红色或黑色。
- 根是黑色。
- 所有叶子都是黑色(叶子是NIL节点)。
- 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
- 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点(简称黑高)
单独记录下旋转操作,还是比较绕口的,旋转操作分为左旋和右旋,左旋是将某个节点旋转为其右孩子的左孩子,而右旋是节点旋转为其左孩子的右孩子。对应下图感受:
类主要属性:
// 比较器,可以定制,
private final Comparator<? super K> comparator;
// 红黑树根节点
private transient Entry<K,V> root;
// 元素个数
private transient int size = 0;
// 操作记录
private transient int modCount = 0;
TreeMap的数据结构(只保留属性):
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;// 节点颜色,默认为true
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
}
插入元素方法一窥:
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) // TreeMap不允许key为null,否则报错,这一点是和HashMap不同的。
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
// t = root,从根节点开始
parent = t;
// 比较t的key插入元素key k>t.key=+n 否则为负
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);// 直到t==null结束循环
}
// 经过上面的循环,parent为t不为空时的最后元素
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;
}
那么我们再来看一下给节点上色重新调整红黑树的方法:
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
// x为root或者x的父节点是红色那么x直接为黑色,这是由红黑树特性决定的
// 叶子节点每个到根的路径不能有连续的两个红色出现
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;
}
还是对应这个图,可能更好理解上面左旋操作和右旋操作两个方法,TreeMap中左旋和右旋的函数方法如下:
private void rotateRight(Entry<K,V> p) { // p=M
if (p != null) {
Entry<K,V> l = p.left; // l = E
p.left = l.right;// p.lt = 子树2
if (l.right != null) l.right.parent = p; // 子树2.parent = M
l.parent = p.parent; // E.parent = null
if (p.parent == null)
root = l; // root = E
else if (p.parent.right == p)
p.parent.right = l;
else p.parent.left = l;
l.right = p;//E.right = M
p.parent = l;// M.parent = E
}
}
private void rotateLeft(Entry<K,V> p) {// p=E
if (p != null) {
Entry<K,V> r = p.right; // r = M
p.right = r.left; // p.rt = 子树2
if (r.left != null)
r.left.parent = p; // 子树2.parent = E
r.parent = p.parent; // M.parent = null
if (p.parent == null)
root = r; // root = M
else if (p.parent.left == p)
p.parent.left = r;
else
p.parent.right = r;
r.left = p; // M.lt = E
p.parent = r; // E.parent = M
}
}
针对上面一通操作,说一下我个人的简单理解,新插入的元素涂为红色,保证它的父节点不是红色即可,然后中间是一通左旋右旋等操作,其实目的是为了达到红黑树的规则,尤其是当顺序存入的时候,如果不进行红黑树调整,会出现数据存储成链表,这就失去了树的优势。以上仅代表个人观点,如有不正确希望大家指正!
树结构是非常常用的数据结构,文件系统,包括数据库的很多内容,java等都有树的很多实现应用,树的更多优势体现在O(log(n))的时间复杂度上,最糟糕的情况也要优于链表O(n)的复杂度,后期还需要单独在学习一下树的算法知识。