0%

Java集合类-TreeMap、ConcurrentSkipListMap

TreeMap作用

  • 元素可以按key保持顺序。
  • 因为有序,提供了很多范围查询的方法,非常方便。

TreeMap的实现

TreeMap实现了NavigableMap接口,该接口又继承SortedMap接口,两个提供了很多范围查询的方法,非常方便。

用红黑树实现,插入、删除、查询的平均时间复杂度都是log(n)。

TreeMap也是非线程安全的,为什么不提供ConcurrentTreeMap?

  1. 因为红黑树的结构调整可能涉及整个树的结点,这样并发下就要锁住很多结点,使用跳表会更局部一点,只锁住局部的几个结点,并发性能更高。
  2. 红黑树加锁实现起来比较复杂,跳表是链表加锁比较容易实现

可以用过Collections.synchronizedSortedMap()保证线程安全,也可以改用ConcurrentSkipListMap并发性能更好。

TreeMap为什么用红黑树不用AVL树?

跟HashMap中的问题一样。

红黑树的3结点可以吸收变化,在多次使用中可以减少调整数据结构的次数,从而提升性能。

ConcurrentSkipListMap是什么?

跳表:链表+多级索引(多层链表)。

  • 给有序链表再增加若干层额外的指针索引,用空间换时间。
  • 每一层都是一个有序链表。
  • 实际效果类似于平衡二叉搜索树。
  • 插入、删除、查询的平均时间复杂度都是log(n)。
  • 支持按key排序所有元素,也支持快速查找。

跳表的数据结构存在什么问题?

按照理想的生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似于一个二分查找,使得查找的时间复杂度可以降低到O(log n)。

但是,这种方法在插入数据的时候有很大的问题。新插入一个节点之后,就会打乱上下相邻两层链表上节点个数严格的2:1的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点(也包括新插入的节点)重新进行调整,这会让时间复杂度重新蜕化成O(n)。删除数据也有同样的问题。

skiplist为了避免这一问题,它不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是为每个节点随机出一个层数(level)。比如,一个节点随机出的层数是3,那么就把它链入到第1层到第3层这三层链表中。

跳表与红黑树区别?

  1. 红黑树为了保持平衡有可能会调整很多结点甚至整个树,而跳表只要修改相邻结点更加的局部,跳表调整次数少性能也就高,同时并发加锁时,锁住的结点更少,可以减少竞争
  2. 跳表的区间查询更高效,因为找到链表头结点顺序遍历就行了,红黑树需要中序遍历相对比较复杂
  3. 跳表占用空间更少,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。

ConcurrentSkipListMap存在什么问题?

size方法求得链表长度要遍历整个链表,并且没有加锁,多线程高并发下这个方法返回值并不准确,高并发下使用size的意义不大。

参考资料