TreeMap的排序是基于对key的排序实现的,它的每一个Entry代表红黑树的一个节点,Entry的数据结构如下:
[java] view plain copy
-
static final class Entry<K,V> implements Map.Entry<K,V> {
-
// 键
-
K key;
-
// 值
-
V value;
-
// 左孩子
-
Entry<K,V> left = null;
-
// 右孩子
-
Entry<K,V> right = null;
-
// 父节点
-
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的构造方法。TreeMap一共有4个构造方法。
1、无参构造方法
[java] view plain copy
-
public TreeMap() {
-
comparator = null;
-
}
采用无参构造方法,不指定比较器,这时候,排序的实现要依赖key.compareTo()方法,因此key必须实现Comparable接口,并覆写其中的compareTo方法。
2、带有比较器的构造方法
[java] view plain copy
-
public TreeMap(Comparator<? super K> comparator) {
-
this.comparator = comparator;
-
}
采用带比较器的构造方法,这时候,排序依赖该比较器,key可以不用实现Comparable接口。
3、带Map的构造方法
[java] view plain copy
-
public TreeMap(Map<? extends K, ? extends V> m) {
-
comparator = null;
-
putAll(m);
-
}
该构造方法同样不指定比较器,调用putAll方法将Map中的所有元素加入到TreeMap中。putAll的源码如下:
[java] view plain copy
-
// 将map中的全部节点添加到TreeMap中
-
public void putAll(Map<? extends K, ? extends V> map) {
-
// 获取map的大小
-
int mapSize = map.size();
-
// 如果TreeMap的大小是0,且map的大小不是0,且map是已排序的“key-value对”
-
if (size==0 && mapSize!=0 && map instanceof SortedMap) {
-
Comparator c = ((SortedMap)map).comparator();
-
// 如果TreeMap和map的比较器相等;
-
// 则将map的元素全部拷贝到TreeMap中,然后返回!
-
if (c == comparator || (c != null && c.equals(comparator))) {
-
++modCount;
-
try {
-
buildFromSorted(mapSize, map.entrySet().iterator(),
-
null, null);
-
} catch (java.io.IOException cannotHappen) {
-
} catch (ClassNotFoundException cannotHappen) {
-
}
-
return;
-
}
-
}
-
// 调用AbstractMap中的putAll();
-
// AbstractMap中的putAll()又会调用到TreeMap的put()
-
super.putAll(map);
-
}
显然,如果Map里的元素是排好序的,就调用buildFromSorted方法来拷贝Map中的元素,这在下一个构造方法中会重点提及,而如果Map中的元素不是排好序的,就调用AbstractMap的putAll(map)方法,该方法源码如下:
[java] view plain copy
-
public void putAll(Map<? extends K, ? extends V> m) {
-
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
-
put(e.getKey(), e.getValue());
-
}
很明显它是将Map中的元素一个个put(插入)到TreeMap中的,主要因为Map中的元素是无序存放的,因此要一个个插入到红黑树中,使其有序存放,并满足红黑树的性质。
4、带有SortedMap的构造方法
[java] view plain copy
-
public TreeMap(SortedMap<K, ? extends V> m) {
-
comparator = m.comparator();
-
try {
-
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
-
} catch (java.io.IOException cannotHappen) {
-
} catch (ClassNotFoundException cannotHappen) {
-
}
-
}
首先将比较器指定为m的比较器,这取决于生成m时调用构造方法是否传入了指定的构造器,而后调用buildFromSorted方法,将SortedMap中的元素插入到TreeMap中,由于SortedMap中的元素师有序的,实际上它是根据SortedMap创建的TreeMap,将SortedMap中对应的元素添加到TreeMap中。
插入操作即对应TreeMap的put方法,put操作实际上只需按照二叉排序树的插入步骤来操作即可,插入到指定位置后,再做调整,使其保持红黑树的特性。put源码的实现:
[java] view plain copy
-
public V put(K key, V value) {
-
Entry<K,V> t = root;
-
// 若红黑树为空,则插入根节点
-
if (t == null) {
-
// TBD:
-
// 5045147: (coll) Adding null to an empty TreeSet should
-
// throw NullPointerException
-
//
-
// compare(key, key); // type check
-
root = new Entry<K,V>(key, value, null);
-
size = 1;
-
modCount++;
-
return null;
-
}
-
int cmp;
-
Entry<K,V> parent;
-
// split comparator and comparable paths
-
Comparator<? super K> cpr = comparator;
-
// 找出(key, value)在二叉排序树中的插入位置。
-
// 红黑树是以key来进行排序的,所以这里以key来进行查找。
-
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();
-
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);
-
}
-
// 为(key-value)新建节点
-
Entry<K,V> e = new Entry<K,V>(key, value, parent);
-
if (cmp < 0)
-
parent.left = e;
-
else
-
parent.right = e;
-
// 插入新的节点后,调用fixAfterInsertion调整红黑树。
-
fixAfterInsertion(e);
-
size++;
-
modCount++;
-
return null;
-
}
这里的fixAfterInsertion便是节点插入后对树进行调整的方法,这里不做介绍。
删除操作及对应TreeMap的deleteEntry方法,deleteEntry方法同样也只需按照二叉排序树的操作步骤实现即可,删除指定节点后,再对树进行调整即可。deleteEntry方法的实现源码如下:
[java] view plain copy
-
// 删除“红黑树的节点p”
-
private void deleteEntry(Entry<K,V> p) {
-
modCount++;
-
size–;
-
if (p.left != null && p.right != null) {
-
Entry<K,V> s = successor §;
-
p.key = s.key;
-
p.value = s.value;
-
p = s;
-
}
-
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
-
if (replacement != null) {
-
replacement.parent = p.parent;
-
if (p.parent == null)
-
root = replacement;
-
else if (p == p.parent.left)
-
p.parent.left = replacement;
-
else
-
p.parent.right = replacement;
-
p.left = p.right = p.parent = null;
-
if (p.color == BLACK)
-
fixAfterDeletion(replacement);
-
} else if (p.parent == null) {
-
root = null;
-
} else {
-
if (p.color == BLACK)
-
fixAfterDeletion§;
-
if (p.parent != null) {
-
if (p == p.parent.left)
-
p.parent.left = null;
-
else if (p == p.parent.right)
-
p.parent.right = null;
-
p.parent = null;
-
}
-
}
-
}
后面的fixAfterDeletion方法便是节点删除后对树进行调整的方法,这里不做介绍。
其他很多方法这里不再一一介绍。
===============================================================
本文对TreeMap的分析较前几篇文章有些浅尝辄止,TreeMap用的没有HashMap那么多,我们有个宏观上的把我和比较即可。
1、TreeMap是根据key进行排序的,它的排序和定位需要依赖比较器或覆写Comparable接口,也因此不需要key覆写hashCode方法和equals方法,就可以排除掉重复的key,而HashMap的key则需要通过覆写hashCode方法和equals方法来确保没有重复的key。
2、TreeMap的查询、插入、删除效率均没有HashMap高,一般只有要对key排序时才使用TreeMap。
3、TreeMap的key不能为null,而HashMap的key可以为null。
注:对TreeSet和HashSet的源码不再进行剖析,二者分别是基于TreeMap和HashMap实现的,只是对应的节点中只有key,而没有value,因此对TreeMap和HashMap比较了解的话,对TreeSet和HashSet的理解就会非常容易。
===
==========================================================================
LinkedHashMap简介
===============
LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加入了一个双向链表的头结点,将所有put到LinkedHashmap的节点一一串成了一个双向循环链表,因此它保留了节点插入的顺序,可以使节点的输出顺序与输入顺序相同。
LinkedHashMap可以用来实现LRU算法(这会在下面的源码中进行分析)。
LinkedHashMap同样是非线程安全的,只在单线程环境下使用。
================================================================================
LinkedHashMap源码如下(加入了详细的注释):
[java] view plain copy
-
package java.util;
-
import java.io.*;
-
public class LinkedHashMap<K,V>
-
extends HashMap<K,V>
-
implements Map<K,V>
-
{
-
private static final long serialVersionUID = 3801124242820219131L;
-
//双向循环链表的头结点,整个LinkedHa只哟shMap中只有一个header,
-
//它将哈希表中所有的Entry贯穿起来,header中不保存key-value对,只保存前后节点的引用
-
private transient Entry<K,V> header;
-
//双向链表中元素排序规则的标志位。
-
//accessOrder为false,表示按插入顺序排序
-
//accessOrder为true,表示按访问顺序排序
-
private final boolean accessOrder;
-
//调用HashMap的构造方法来构造底层的数组
-
public LinkedHashMap(int initialCapacity, float loadFactor) {
-
super(initialCapacity, loadFactor);
-
accessOrder = false; //链表中的元素默认按照插入顺序排序
-
}
-
//加载因子取默认的0.75f
-
public LinkedHashMap(int initialCapacity) {
-
super(initialCapacity);
-
accessOrder = false;
-
}
-
//加载因子取默认的0.75f,容量取默认的16
-
public LinkedHashMap() {
-
super();
-
accessOrder = false;
-
}
-
//含有子Map的构造方法,同样调用HashMap的对应的构造方法
-
public LinkedHashMap(Map<? extends K, ? extends V> m) {
-
super(m);
-
accessOrder = false;
-
}
-
//该构造方法可以指定链表中的元素排序的规则
-
public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
-
super(initialCapacity, loadFactor);
-
this.accessOrder = accessOrder;
-
}
-
//覆写父类的init()方法(HashMap中的init方法为空),
-
//该方法在父类的构造方法和Clone、readObject中在插入元素前被调用,
-
//初始化一个空的双向循环链表,头结点中不保存数据,头结点的下一个节点才开始保存数据。
-
void init() {
-
header = new Entry<K,V>(-1, null, null, null);
-
header.before = header.after = header;
-
}
-
//覆写HashMap中的transfer方法,它在父类的resize方法中被调用,
-
//扩容后,将key-value对重新映射到新的newTable中
-
//覆写该方法的目的是为了提高复制的效率,
-
//这里充分利用双向循环链表的特点进行迭代,不用对底层的数组进行for循环。
-
void transfer(HashMap.Entry[] newTable) {
-
int newCapacity = newTable.length;
-
for (Entry<K,V> e = header.after; e != header; e = e.after) {
-
int index = indexFor(e.hash, newCapacity);
-
e.next = newTable[index];
-
newTable[index] = e;
-
}
-
}
-
//覆写HashMap中的containsValue方法,
-
//覆写该方法的目的同样是为了提高查询的效率,
-
//利用双向循环链表的特点进行查询,少了对数组的外层for循环
-
public boolean containsValue(Object value) {
-
// Overridden to take advantage of faster iterator
-
if (value==null) {
-
for (Entry e = header.after; e != header; e = e.after)
-
if (e.value==null)
-
return true;
-
} else {
-
for (Entry e = header.after; e != header; e = e.after)
-
if (value.equals(e.value))
-
return true;
-
}
-
return false;
-
}
-
//覆写HashMap中的get方法,通过getEntry方法获取Entry对象。
-
//注意这里的recordAccess方法,
-
//如果链表中元素的排序规则是按照插入的先后顺序排序的话,该方法什么也不做,
-
//如果链表中元素的排序规则是按照访问的先后顺序排序的话,则将e移到链表的末尾处。
-
public V get(Object key) {
-
Entry<K,V> e = (Entry<K,V>)getEntry(key);
-
if (e == null)
-
return null;
-
e.recordAccess(this);
-
return e.value;
-
}
-
//清空HashMap,并将双向链表还原为只有头结点的空链表
-
public void clear() {
-
super.clear();
-
header.before = header.after = header;
-
}
-
//Enty的数据结构,多了两个指向前后节点的引用
-
private static class Entry<K,V> extends HashMap.Entry<K,V> {
-
// These fields comprise the doubly linked list used for iteration.
-
Entry<K,V> before, after;
-
//调用父类的构造方法
-
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
-
super(hash, key, value, next);
-
}
-
//双向循环链表中,删除当前的Entry
-
private void remove() {
-
before.after = after;
-
after.before = before;
-
}
-
//双向循环立链表中,将当前的Entry插入到existingEntry的前面
-
private void addBefore(Entry<K,V> existingEntry) {
-
after = existingEntry;
-
before = existingEntry.before;
-
before.after = this;
-
after.before = this;
-
}
-
//覆写HashMap中的recordAccess方法(HashMap中该方法为空),
-
//当调用父类的put方法,在发现插入的key已经存在时,会调用该方法,
-
//调用LinkedHashmap覆写的get方法时,也会调用到该方法,
-
//该方法提供了LRU算法的实现,它将最近使用的Entry放到双向循环链表的尾部,
-
//accessOrder为true时,get方法会调用recordAccess方法
-
//put方法在覆盖key-value对时也会调用recordAccess方法
-
//它们导致Entry最近使用,因此将其移到双向链表的末尾
-
void recordAccess(HashMap<K,V> m) {
-
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
-
//如果链表中元素按照访问顺序排序,则将当前访问的Entry移到双向循环链表的尾部,
-
//如果是按照插入的先后顺序排序,则不做任何事情。
-
if (lm.accessOrder) {
-
lm.modCount++;
-
//移除当前访问的Entry
-
remove();
-
//将当前访问的Entry插入到链表的尾部
-
addBefore(lm.header);
-
}
-
}
-
void recordRemoval(HashMap<K,V> m) {
-
remove();
-
}
-
}
-
//迭代器
-
private abstract class LinkedHashIterator implements Iterator {
-
Entry<K,V> nextEntry = header.after;
-
Entry<K,V> lastReturned = null;
-
/**
-
* The modCount value that the iterator believes that the backing
-
* List should have. If this expectation is violated, the iterator
-
* has detected concurrent modification.
-
*/
-
int expectedModCount = modCount;
-
public boolean hasNext() {
-
return nextEntry != header;
-
}
-
public void remove() {
-
if (lastReturned == null)
-
throw new IllegalStateException();
-
if (modCount != expectedModCount)
-
throw new ConcurrentModificationException();
-
LinkedHashMap.this.remove(lastReturned.key);
-
lastReturned = null;
-
expectedModCount = modCount;
-
}
-
//从head的下一个节点开始迭代
-
Entry<K,V> nextEntry() {
-
if (modCount != expectedModCount)
-
throw new ConcurrentModificationException();
-
if (nextEntry == header)
-
throw new NoSuchElementException();
-
Entry<K,V> e = lastReturned = nextEntry;
-
nextEntry = e.after;
-
return e;
-
}
-
}
-
//key迭代器
-
private class KeyIterator extends LinkedHashIterator {
-
public K next() { return nextEntry().getKey(); }
-
}
-
//value迭代器
-
private class ValueIterator extends LinkedHashIterator {
-
public V next() { return nextEntry().value; }
-
}
-
//Entry迭代器
-
private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {
-
public Map.Entry<K,V> next() { return nextEntry(); }
-
}
-
// These Overrides alter the behavior of superclass view iterator() methods
-
Iterator newKeyIterator() { return new KeyIterator(); }
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
小编精心为大家准备了一手资料
以上Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术
【附】架构书籍
- BAT面试的20道高频数据库问题解析
- Java面试宝典
- Netty实战
- 算法
BATJ面试要点及Java架构师进阶资料
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**[外链图片转存中…(img-EJ3fOFBg-1713178349204)]
[外链图片转存中…(img-dcZcw28Z-1713178349204)]
[外链图片转存中…(img-QCsYIP7B-1713178349204)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
小编精心为大家准备了一手资料
[外链图片转存中…(img-tKRg1nmD-1713178349205)]
[外链图片转存中…(img-b40OEpGX-1713178349205)]
以上Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术
【附】架构书籍
- BAT面试的20道高频数据库问题解析
- Java面试宝典
- Netty实战
- 算法
[外链图片转存中…(img-HgEo8wsC-1713178349205)]
BATJ面试要点及Java架构师进阶资料
[外链图片转存中…(img-sdPDkN4j-1713178349205)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!