Java集合之TreeMap 谁借莪1个温暖的怀抱¢ 2021-08-13 20:28 409阅读 0赞 ### TreeMap ### * 1. 简介 * 2. 继承体系 * 3. 深入源码 * * 3.1 属性 * 3.2 内部类 * 3.3 构造方法 * 3.5 获取元素 * 3.6 添加元素 * 删除元素 * 4. 总结 # 1. 简介 # `TreeMap` 只使用红黑树(并没有使用其他数据结构)存储元素,可以保证元素按`key`值的按照排序规则进行存储。 # 2. 继承体系 # ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdfemhhb18_size_16_color_FFFFFF_t_70] `TreeMap` 实现了 `SortedMap` 接口,所以其可以根据`key`实现的 `Comparable` 进行排序或者根据自定义的 `Comparator` 对 `key`进行排序。 public interface SortedMap<K,V> extends Map<K,V> { /** * 返回用于在此映射中对键进行排序的比较器; */ Comparator<? super K> comparator(); /** * 返回 key 在范围 fromKey 和 toKey 之间的数据; */ SortedMap<K,V> subMap(K fromKey, K toKey); /** * 返回小于 toKey 的数据 */ SortedMap<K,V> headMap(K toKey); /** * 返回大于等于 fromKey 的数据 */ SortedMap<K,V> tailMap(K fromKey); /** * 返回最小的 key */ K firstKey(); /** * 返回最大的key */ K lastKey(); /** * 返回所有的 key */ Set<K> keySet(); /** * 返回所有的value */ Collection<V> values(); /** * 返回所有的键值对 */ Set<Map.Entry<K, V>> entrySet(); } # 3. 深入源码 # ## 3.1 属性 ## /** * 比较器,如果为null,则按 key 的实现的 Comparable 进行排序 */ private final Comparator<? super K> comparator; /** * 红黑树的根节点 */ private transient Entry<K,V> root; /** * 键值对的个数 */ private transient int size = 0; /** * 修改次数,快速失败策略 */ private transient int modCount = 0; ## 3.2 内部类 ## // 红黑树节点 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; } ## 3.3 构造方法 ## /** * 默认构造方法,key必须实现Comparable接口 */ public TreeMap() { comparator = null; } /** * 传入 compartor 比较器对 key 进行排序 */ public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } /** * m 中的 key 必须实现Comparable接口 */ public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); } 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) { } } ## 3.5 获取元素 ## public V get(Object key) { Entry<K,V> p = getEntry(key); return (p==null ? null : p.value); } 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) { 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) { 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; } (1)从`root`遍历整个树; (2)如果待查找的`key`比当前遍历的`key`小,则在其左子树中查找; (3)如果待查找的`key`比当前遍历的`key`大,则在其右子树中查找; (4)如果待查找的`key`与当前遍历的`key`相等,则找到了该元素,直接返回; (5)从这里可以看出是否有`comparator`分化成了两个方法,但是内部逻辑一模一样 ## 3.6 添加元素 ## 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; // 根据是否有comparator使用不同的分支 Comparator<? super K> cpr = comparator; if (cpr != null) { do { // 如果使用的是comparator方式,key值可以为null,只要在comparator.compare()中允许即可 // 从根节点开始遍历寻找 parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) // 如果小于0从左子树寻找 t = t.left; else if (cmp > 0) // 如果大于0从右子树寻找 t = t.right; else // 如果等于0,说明插入的节点已经存在了,直接更换其value值并返回旧值 return t.setValue(value); } while (t != null); } else { // 如果使用的是Comparable方式,key不能为null 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) // 如果小于0从左子树寻找 t = t.left; else if (cmp > 0) // 如果大于0从右子树寻找 t = t.right; else // 如果等于0,说明插入的节点已经存在了,直接更换其value值并返回旧值 return t.setValue(value); } while (t != null); } // 如果没找到,那么新建一个节点,并插入到树中 Entry<K,V> e = new Entry<>(key, value, parent); if (cmp < 0) // 如果小于0插入到左子节点 parent.left = e; else // 如果大于0插入到右子节点 parent.right = e; // 按照红黑树的规则进行平衡 fixAfterInsertion(e); size++; modCount++; return null; } 插入元素的本质便是按照指定的规则在排序树种=中插入节点,最后再以红黑树的方式进行平衡。 ## 删除元素 ## 删除元素同插入元素,先按照指定的规则在排序树中寻找要删除节点,最后再以红黑树的方式进行平衡。 public V remove(Object key) { // 获取节点 Entry<K,V> p = getEntry(key); if (p == null) return null; V oldValue = p.value; // 删除节点 deleteEntry(p); // 返回删除的value return oldValue; } private void deleteEntry(Entry<K,V> p) { // 修改次数加1 modCount++; // 元素个数减1 size--; if (p.left != null && p.right != null) { // 如果当前节点既有左子节点,又有右子节点 // 取其右子树中最小的节点 Entry<K,V> s = successor(p); // 用右子树中最小节点的值替换当前节点的值 p.key = s.key; p.value = s.value; // 把右子树中最小节点设为当前节点 p = s; // 这种情况实际上并没有删除p节点,而是把p节点的值改了,实际删除的是p的后继节点 } // 如果原来的当前节点(p)有2个子节点,则当前节点已经变成原来p的右子树中的最小节点了,也就是说其没有左子节点了 // 到这一步,p肯定只有一个子节点了 // 如果当前节点有子节点,则用子节点替换当前节点 Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { // 把替换节点直接放到当前节点的位置上(相当于删除了p,并把替换节点移动过来了) 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的各项属性都设为空 p.left = p.right = p.parent = null; // 如果p是黑节点,则需要再平衡 if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null) { // 如果当前节点就是根节点,则直接将根节点设为空即可 root = null; } else { // 如果当前节点没有子节点且其为黑节点,则把自己当作虚拟的替换节点进行再平衡 if (p.color == BLACK) fixAfterDeletion(p); // 平衡完成后删除当前节点(与父节点断绝关系) 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; } } } # 4. 总结 # 红黑树的特性: (1)每个节点或者是黑色,或者是红色。 (2)根节点是黑色。 (3)每个叶子节点(NIL)是黑色。(注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!) (4)如果一个节点是红色的,则它的子节点必须是黑色的。 (5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。 `TreeMap`的特性: (1)`TreeMap`的存储结构只有一颗红黑树; (2)`TreeMap`中的元素是有序的,按`key`的顺序排列; (3)`TreeMap`比`HashMap`要慢一些,因为`HashMap`前面还做了一层桶,寻找元素要快很多; (4)`TreeMap`没有扩容的概念; (5)`TreeMap`的遍历不是采用传统的递归式遍历; (6)`TreeMap`可以按范围查找元素,查找最近的元素; [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdfemhhb18_size_16_color_FFFFFF_t_70]: /images/20210813/871a342212f44689b7756d2eb75208cd.png
还没有评论,来说两句吧...