Java笔记(八)TreeMap & TreeSet & LinkedHashMap
TreeMap & TreeSet & LinkedHashMap
一、TreeMap
HashMap缺陷:键值对之间没有特定的顺序。在TreeMap中,
键值对之间按键有序,TreeMap的实现基础是排序二叉树。
一)基本用法
构造方法:
- //无参构造方法要求Map中的键实现Compareble接口
- public TreeMap()
- //如果comparator不为null,在TreeMap内部进行比较时会调用compare方法
- public TreeMap(Comparator<? super K> comparator)
TreeMap按键的比较结果对键进行重排,即使键实际上不同,但只要比较
结果相同,它们就会被认为相同,键只会保留一份。
- TreeMap<String, String> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- map.put("t", "try");
- map.put("T", "order");
- for (Map.Entry<String, String> kv : map.entrySet()) {
- System.out.println(kv.getKey() + " = " + kv.getValue()); //t = order
- }
二)实现原理
TreeMap内部是用红黑树实现的,红黑树是一种大致平衡的排序二叉树。
1)内部组成
内部主要成员:
- private final Comparator<? super K> comparator;
- private transient Entry<K,V> root = null; //指向二叉树根节点
- private transient int size = 0;
内部类Entry:
- 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;
- }
- }
2)保存键值对
put方法代码,添加第一个节点的情况:
- 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;
- }
- //…
如果不是第一次添加,寻找父节点,寻找父节点根据是否设置了comparator分为两种情况:
- 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();
- 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);
- }
基本思路:循环比较找到父节点,并插入作为其左孩子或者右孩子,然后调整保持树的大致平衡。
3)根据键获取值
- 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();
- 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;
- }
4)查看是否包含某个值
按值查找需要遍历
- public boolean containsValue(Object value) {
- for(Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
- if(valEquals(value, e.value))
- return true;
- return false;
- }
- final Entry<K,V> getFirstEntry() {
- Entry<K,V> p = root;
- if(p != null)
- while (p.left != null)
- p = p.left;
- return p;
- }
- static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
- if(t == null)
- return null;
- else if(t.right != null) {
- Entry<K,V> p = t.right;
- while (p.left != null)
- p = p.left;
- return p;
- } else {
- Entry<K,V> p = t.parent;
- Entry<K,V> ch = t;
- while(p != null && ch == p.right) {
- ch = p;
- p = p.parent;
- }
- return p;
- }
- }
三)小结
ThreeMap根据键保存、查找、删除的效率比较高,为O(h),h为树的高度。
不要求排序优先考虑HashMap。
二、TreeSet
一)基本用法
TreeSet实现了两点:排重和有序
构造函数:
- public TreeSet()
- public TreeSet(Comparator<? super E> comparator)
二)实现原理
TreeSet是基于TreeMap实现的:
- private transient NavigableMap<E,Object> m; //背后的TreeMap
- private static final Object PRESENT = new Object(); //固定值
- TreeSet(NavigableMap<E,Object> m) {
- this.m = m;
- }
- public TreeSet() {
- this(new TreeMap<E,Object>());
- }
三)小结
没有重复元素,通过TreeMap实现。
三、LinkedHashMap
LinkedHashMap是HashMap的子类,可以保持元素按插入或者访问有序。
一)基本用法
该类内部有一个双向链表维护键值对顺序,每个键值对既位于哈希表中,也位于双向链表中。
该类支持两种顺序:
1)插入顺序:先添加的在前面,后添加的在后面,修改不影响顺序。
2)访问顺序:所谓访问就是get/put操作,对一个键执行get/put操作后,
其对应的键值会移到链表末尾。
LinkedHashMap有5个构造方法,其中4个都是按插入顺序,只有一个构造
方法可以指定按访问顺序:
- public LinkedHashMap(int initialCapacity, float loadFactor,
- boolean accessOrder) //accessOrder为ture就是按顺序访问
默认情况下LinkedHashMap是按插入有序的:
- LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
- map.put("c", 56);
- map.put("d", 22);
- map.put("a", 33);
- for (Map.Entry<String, Integer> entry : map.entrySet()) {
- System.out.println(entry.getKey() + " = " + entry.getValue());
- }
- /*c = 56
- d = 22
- a = 33*/
插入有序一种常见的使用场景:希望Map按键有序,键在添加前已经排好序,
此时就没必要使用开销大的TreeMap。
- LinkedHashMap<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
- map.put("c", 56);
- map.put("d", 22);
- map.put("a", 33);
- map.get("c");
- map.put("d", 66);
- for (Map.Entry<String, Integer> entry : map.entrySet()) {
- System.out.println(entry.getKey() + " = " + entry.getValue());
- }
- /*a = 33
- c = 56
- d = 66*/
二、实现原理
该类内部实例变量:
- private transient Entry<K,V> header; //双向链表的表头
- private final boolean accessOrder; //是否按访问顺序
Entry内部类:
- private static class Entry<K,V> extends HashMap.Entry<K,V> {
- Entry<K,V> before, after;
- Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
- super(hash, key, value, next);
- }
- private void remove() {
- before.after = after;
- after.before = before;
- }
- private void addBefore(Entry<K,V> existingEntry) {
- after = existingEntry;
- before = existingEntry.before;
- before.after = this;
- after.before = this;
- }
- void recordAccess(HashMap<K,V> m) {
- LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
- if(lm.accessOrder) {
- lm.modCount++;
- remove();
- addBefore(lm.header);
- }
- }
- void recordRemoval(HashMap<K,V> m) {
- remove();
- }
- }
init用于初始化链表的头节点:
- void init() {
- header = new Entry<>(-1, null, null, null);
- header.before = header.after = header;
- }
在LinkedHashMap中,put方法还会将节点加入到链表中来,如果是
按访问有序的,还会调整节点到末尾,并根据情况删除掉最久没有被访问的节点。
HashMap的put实现中,如果是新的键,会调用addEntry方法添加节点,LinkedHashMap重写了该方法:
- void addEntry(int hash, K key, V value, int bucketIndex) {
- super.addEntry(hash, key, value, bucketIndex);
- //Remove eldest entry if instructed
- Entry<K,V> eldest = header.after;
- if(removeEldestEntry(eldest)) {
- removeEntryForKey(eldest.key);
- }
- }
他先调用父类的addEntry方法,父类的addEntry会调用createEntry创建节点,
LinkedHashMap重写了createEntry方法:
- void createEntry(int hash, K key, V value, int bucketIndex) {
- HashMap.Entry<K,V> old = table[bucketIndex];
- Entry<K,V> e = new Entry<>(hash, key, value, old);
- table[bucketIndex] = e;
- //新建的节点,加入到链表末尾
- e.addBefore(header);
- size++;
- }
例如执行:
- Map<String,Integer> countMap = new LinkedHashMap<>();
- countMap.put("hello", 1);
执行后内存结构:
在HashMap的put实现中,如果键已经存在,则会调用节点的recordAccess方法。
LinkedHashMap.Entry重写了该方法,如果是有序访问,则调整该节点到链表末尾。
Java笔记(八)TreeMap & TreeSet & LinkedHashMap的更多相关文章
- Java笔记(八)……数组
数组的概念 同一种类型数据的集合.其实数组就是一个容器. 数组的好处 可以自动给数组中的元素从0开始编号,方便操作这些元素. 数组的格式 元素类型[] 数组名 = new 元素类型[个数]; int[ ...
- Java TreeMap 和 LinkedHashMap【笔记】
Java TreeMap 和 LinkedHashMap[笔记] TreeMap TreeMap基本结构 TreeMap 底层的数据结构就是红黑树,和 HashMap 的红黑树结构一样 与HashMa ...
- 【Java】Map杂谈,hashcode()、equals()、HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap
参考的优秀文章: <Java编程思想>第四版 <Effective Java>第二版 Map接口是映射表的结构,维护键对象与值对象的对应关系,称键值对. > hashco ...
- Java IO学习笔记八:Netty入门
作者:Grey 原文地址:Java IO学习笔记八:Netty入门 多路复用多线程方式还是有点麻烦,Netty帮我们做了封装,大大简化了编码的复杂度,接下来熟悉一下netty的基本使用. Netty+ ...
- Java学习笔记四:Java的八种基本数据类型
Java的八种基本数据类型 Java语言提供了八种基本类型.六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型. Java基本类型共有八种,基本类型可以分为三类,字符类型char,布 ...
- Java 集合系列 17 TreeSet
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- java中的TreeMap如何顺序按照插入顺序排序
java中的TreeMap如何顺序按照插入顺序排序 你可以使用LinkedHashMap 这个是可以记住插入顺序的. 用LinkedHashMap吧.它内部有一个链表,保持插入的顺序.迭代的时候,也 ...
- Java提高十七:TreeSet 深入分析
前一篇我们分析了TreeMap,接下来我们分析TreeSet,比较有意思的地方是,似乎有Map和Set的地方,Set几乎都成了Map的一个马甲.此话怎讲呢?在前面一篇讨论HashMap和HashSet ...
- Java笔记(十六)并发容器
并发容器 一.写时复制的List和Set CopyOnWrite即写时复制,或称写时拷贝,是解决并发问题的一种重要思路. 一)CopyOnWriteArrayList 该类实现了List接口,它的用法 ...
随机推荐
- cf1110d 线性dp
很精练的一道题 /* dp[i][j][k]表示值i作为最大值结束的边剩k条,i-1剩下j条的情况的结果 dp[i][k][l]是由dp[i-1][j][k]的j决定的,因为k+l是被留下给后面用的, ...
- 谷歌、火狐浏览器 缩放为80% 时,margin值才正确
声明:小白的笔记,欢迎大神指点.联系QQ:1522025433. 在网页布局中,通过 谷歌浏览器或火狐浏览器 预览时,发现我们定义的盒模型width,height,margin,padding 值都是 ...
- Python继承、方法重写
继承 在编写类时,并不是每次都要从空白开始.当要编写的类和另一个已经存在的类之间存在一定的继承关系时,就可以通过继承来达到代码重用的目的,提高开发效率. class one(): "&quo ...
- MAKEWORD 宏(macro)
先看看Microsoft给出的关于MAKEWORD的参考: 从Microsoft给出的参考可以得知,宏MAKEWORD的作用是用于创建一个由bHigh和bLow组成的WORD类型的值. 其中bLow是 ...
- MySQL5.7.11版本,报错Cannot proceed because system tables used by Event Scheduler were found damaged at server start
解决思路: 1. 在MySQL安装目录下执行./mysql_upgrade -uroot -p,此处是为了更新MySQL的系统表,在5.6之前的版本上,更新系统表的命令是mysql_fix_privi ...
- easyui组件window拖动时超过浏览器顶部则无法拖回
项目研发过程中遇到一个问题,easyui的window可以随意拖动或者放大缩小,但是鼠标只有放在“header”上面时鼠标箭头才会变成四个方向的箭头,也就是只有在这时才能拖动窗口:但是当拖动的窗口超过 ...
- selenium 操作复选框
场景 从上一节的例子中可以看出,webdriver可以很方便的使用findElement方法来定位某个特定的对象,不过有时候我们却需要定位一组对象, 这时候就需要使用findElements方法. 定 ...
- python提取文件中的方法名称
#提取文件中的方法名称 # -*- coding:utf-8 -*- def Query_Method(filepath): file = open(filepath,'r',encoding= 'U ...
- Python sendmail
#coding:utf- #强制使用utf-8编码格式 import smtplib #加载smtplib模块 from email.mime.text import MIMEText from em ...
- 认识IQueryable和IQueryProvider接口
1.Func<Student, bool>和Expression<Func<Student, bool>>的区别 class Program { static vo ...