欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。
欢迎跳转到本文的原文链接:https://honeypps.com/java/java-collection-treemap/
TreeMap定义
package java.util;
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable{
}
public interface NavigableMap<K,V> extends SortedMap<K,V>{}
- TreeMap是非线程安全的。
可以采用这种方式将TreeMap设置为同步的:Map m = Collections.synchronizedSortedMap(new TreeMap(…)); - TreeMap是用键来进行升序顺序来排序的。通过Comparable 或 Comparator来排序。
TreeMap是SortedMap接口的基于红黑树的实现。此类保证了映射按照升序顺序排列关键字, 根据使用的构造方法不同,可能会按照键的类的自然顺序进行排序,或者按照创建时所提供的比较器(自定义)进行排序。
先举个案例说明一下:
TreeMap继承AbstractMap,实现NavigableMap、Cloneable、Serializable三个接口。其中AbstractMap表明TreeMap为一个Map即支持key-value的集合, NavigableMap则意味着它支持一系列的导航方法,具备针对给定搜索目标返回最接近匹配项的导航方法 。
TreeMap中同时也包含了如下几个重要的属性:
//比较器,因为TreeMap是有序的,通过comparator接口我们可以对TreeMap的内部排序进行精密的控制
private final Comparator<? super K> comparator;
//TreeMap红-黑节点,为TreeMap的内部类
private transient Entry<K,V> root = null;
//容器大小
private transient int size = 0;
//TreeMap修改次数
private transient int modCount = 0;
//红黑树的节点颜色–红色
private static final boolean RED = false;
//红黑树的节点颜色–黑色
private static final boolean BLACK = true;
对于实体节点Entry是TreeMap的内部类,它有几个重要的属性:
//键
K key;
//值
V value;
//左孩子
Entry<K,V> left = null;
//右孩子
Entry<K,V> right = null;
//父亲
Entry<K,V> parent;
//颜色
boolean color = BLACK;
关于红黑树的实现可以搜索一些相关资料,我觉得参考资料3就不错,博主就不赘述了。
TreeMap特点
Map<Integer,Integer> map = new TreeMap<>();
map.put(1, null);
map.put(10, 1);
map.put(3, 2);
map.put(2, 3);
// map.put(null, 3);
for(Entry<Integer, Integer> entry:map.entrySet())
{
System.out.println(entry.getKey()+":"+entry.getValue());
}
- 7
- 8
- 9
- 10
- 11
输出结果:
1:null
2:3
3:2
10:1
- 1
- 2
- 3
- 4
也可以采用自定义的类,引用《Comparable与Comparator浅析》中的Person1举例:
public class Person1 implements Comparable<Person1>
{
private int age;
private String name;
public Person1(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person1 o)
{
return this.age-o.age;
}
@Override
public String toString()
{
return name+":"+age;
}
}
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
测试代码:
Map<Person1,Integer> map = new TreeMap<>();
Person1 person1 = new Person1("zzh",18);
Person1 person2 = new Person1("jj",17);
Person1 person3 = new Person1("qq",19);
map.put(person1, 1);
map.put(person2, 2);
map.put(person3, 3);
for(Entry<Person1, Integer> entry:map.entrySet())
{
System.out.println(entry.getKey()+":"+entry.getValue());
}
- 7
- 8
- 9
- 10
- 11
测试结果:
jj:17:2
zzh:18:1
qq:19:3
- 1
- 2
- 3
只要自定义键的类实现了Comparable接口就可以使用TreeMap的排序功能,也可以通过Comparator接口实现(关于Comparable和Comparator的区别可以翻看《Comparable与Comparator浅析》),如下:
首先引用类Person2:
public final class Person2
{
private int age;
private String name;
public Person2(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public String toString()
{
return name+":"+age;
}
//getter and setter方法省略....
}
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
测试代码:
Map<Person2,Integer> map2 = new TreeMap<>(new Comparator<Person2>(){
@Override
public int compare(Person2 o1, Person2 o2)
{
if(o1 == null || o2 == null)
return 0;
return o1.getAge()-o2.getAge();
}
});
Person2 p1 = new Person2("zzh",18);
Person2 p2 = new Person2("jj",17);
Person2 p3 = new Person2("qq",19);
map2.put(p1, 1);
map2.put(p2, 2);
map2.put(p3, 3);
for(Entry<Person2, Integer> entry:map2.entrySet())
{
System.out.println(entry.getKey()+":"+entry.getValue());
}
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
输出结果:
jj:17:2
zzh:18:1
qq:19:3
- 1
- 2
- 3
可以看到Person2中并没有实现Comparable接口,但是想要和Person1一样能够和TreeMap合作,只需要在创建TreeMap的构造函数中声明一个Comparator接口的实现,譬如上面的例子所示。
3. 由所有此类的“collection 视图方法”所返回的迭代器都是快速失败的。
这点和HashMap一样,所谓快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其结构性的修改,这是迭代器会立马感知到,并且立刻抛出ConcurrentModificationException异常,而不是等待迭代完成之后才告诉你已经出错。注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。 快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。 因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。
4. 和HashMap一样,如果插入重复的元素,后面的元素会覆盖前面的。
5. 键不可以为null(如果比较器对null做了处理,就可以为null),但是值可以为null。
如上面的案例中,加入:
Person2 p4 = new Person2(null,19);
map2.put(p4, 4);
- 1
- 2
在打印map2的时候就不会报错。
6. TreeMap对containsKey、get、put 和 remove 操作提供了保证的 log(n) 时间开销。
TreeMap提供了很多方法方便大小使用,譬如containsKey, get, put,remove,entrySet等Map通用的方法,也包括fisrtEntry, firstKey, cellingKey, lowerKey等方法,由于篇幅较大,不便赘述,使用前最好翻看一下API或者源码熟悉一下。
HashMap与TreeMap的区别
- 实现方式
HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。
(1)HashMap(): 构建一个空的哈希映像
(2)HashMap(Map m): 构建一个哈希映像,并且添加映像m的所有映射
(3)HashMap(int initialCapacity): 构建一个拥有特定容量的空的哈希映像
(4)HashMap(int initialCapacity, float loadFactor): 构建一个拥有特定容量和加载因子的空的哈希映像
TreeMap:基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。
(1)TreeMap():构建一个空的映像树
(2)TreeMap(Map m): 构建一个映像树,并且添加映像m中所有元素
(3)TreeMap(Comparator c): 构建一个映像树,并且使用特定的比较器对关键字进行排序
(4)TreeMap(SortedMap s): 构建一个映像树,添加映像树s中所有映射,并且使用与有序映像s相同的比较器排序 - 用途
HashMap:适用于在Map中插入、删除和定位元素。
TreeMap:适用于按自然顺序或自定义顺序遍历键(key)。
HashMap通常比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在需要排序的Map时候才用TreeMap.
参考资料
欢迎跳转到本文的原文链接:https://honeypps.com/java/java-collection-treemap/
欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。