Java TreeSet

TreeMap 原理实现及常用方法 TreeSet 是一个有序集合,它扩展了 AbstractSet 类并实现了 NavigableSet 接口。

  • 它存储唯一的元素
  • 它不保留元素的插入顺序
  • 它按升序对元素进行排序
  • 它不是线程安全的

在该实现中,对象根据其自然顺序以升序排序和存储。该 TreeSet 中使用平衡树,更具体的一个 红黑树

简单地说,作为 自平衡二叉搜索树,二叉树的每个节点包括一个额外的位,用于识别红色或黑色的节点的颜色。在随后的插入和删除期间,这些“颜色”位有助于确保树保持或多或少的平衡。

Set<String> treeSet = new TreeSet<>();
Set<String> treeSet = new TreeSet<>(Comparator.comparing(String::length));

虽然 TreeSet 不是线程安全的,但可以使用 Collections.synchronizedSet() 包装器在外部进行同步:

Set<String> syncTreeSet = Collections.synchronizedSet(treeSet);

TreeSet add

可用于将元素添加到一个 TreeSet 中。如果成功添加了元素,则该方法返回 true,否则返回 false。
该方法的声明只有当 Set 中不存在该元素时才会添加该元素。

Set<String> treeSet = new TreeSet<>();
assertTrue(treeSet.add("String Added"));

该方法的实现细节说明了如何 TreeSet 的内部工作,它如何利用 TreeMap 中的 放方法来存储元素:

public boolean add(E e) {
    return m.put(e, PRESENT) == null;
}

变量 m 指的是内部支持 TreeMap(注意TreeMap实现了NavigateableMap):

private transient NavigableMap<E, Object> m;

因此,TreeSet 在内部依赖于后备 NavigableMap,当创建 TreeSet 的实例时,它会使用 TreeMap 实例进行初始化:

public TreeSet() {
    this(new TreeMap<E,Object>());
}

TreeSet contains

检查一个给定的元素是否存在于一个给定的 TreeSet 中。如果找到该元素,则返回 true,否则返回 false。

Set<String> treeSetContains = new TreeSet<>();
treeSetContains.add("String Added");
assertTrue(treeSetContains.contains("String Added"));

TreeSet remove

从该组中删除指定的元素,如果它是存在。
如果集合包含指定的元素,则此方法返回 true。

Set<String> removeFromTreeSet = new TreeSet<>();
removeFromTreeSet.add("String Added");
assertTrue(removeFromTreeSet.remove("String Added"));

TreeSet clear

从集合中删除所有项

Set<String> clearTreeSet = new TreeSet<>();
clearTreeSet.add("String Added");
clearTreeSet.clear();
assertTrue(clearTreeSet.isEmpty());

TreeSet size

size() 方法被用于识别存在于该TreeSet中元素的数量。它是API中的基本方法之一:

Set<String> treeSetSize = new TreeSet<>();
treeSetSize.add("String Added");
assertEquals(1, treeSetSize.size());

TreeSet isEmpty()

所述的isEmpty()方法可用于找出如果一个给定的TreeSet的实例是空的或不是:

Set<String> emptyTreeSet = new TreeSet<>();
assertTrue(emptyTreeSet.isEmpty());

TreeSet iterator

所述iterator() 方法返回迭代以升序过在元件迭代集。

Set<String> treeSet = new TreeSet<>();
treeSet.add("First");
treeSet.add("Second");
treeSet.add("Third");
Iterator<String> itr = treeSet.iterator();
while (itr.hasNext()) {
    System.out.println(itr.next());
}

此外,TreeSet 使我们能够按降序迭代 Set。

TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("First");
treeSet.add("Second");
treeSet.add("Third");
Iterator<String> itr = treeSet.descendingIterator();
while (itr.hasNext()) {
    System.out.println(itr.next());
}

如果 Iterator 被创建之后,只能通过迭代器 remove() 方法。其它任何方式在集合上删除元素都将抛出ConcurrentModificationException 异常。

Set<String> treeSet = new TreeSet<>();
treeSet.add("First");
treeSet.add("Second");
treeSet.add("Third");
Iterator<String> itr = treeSet.iterator();
while (itr.hasNext()) {
    itr.next();
    treeSet.remove("Second");
}

或者,如果使用了迭代器的 remove 方法,那么我们就不会遇到异常:

Set<String> treeSet = new TreeSet<>();
treeSet.add("First");
treeSet.add("Second");
treeSet.add("Third");
Iterator<String> itr = treeSet.iterator();
while (itr.hasNext()) {
    String element = itr.next();
    if (element.equals("Second"))
       itr.remove();
}

assertEquals(2, treeSet.size());

不能保证迭代器的 fail-fast 事件行为,因为在存在不同步的并发修改时不可能做出任何硬性保证。

fail-fast 机制是 java 集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast 事件。例如:当某一个线程 A 通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程 A 访问集合时,就会抛出 ConcurrentModificationException 异常,产生 fail-fast 事件。

TreeSet first

如果 TreeSet 不为空,则此方法返回 TreeSet 中的第一个元素。否则,它会抛出 NoSuchElementException。

TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("First");
assertEquals("First", treeSet.first());

TreeSet last

与上面的示例类似,如果集合不为空,此方法将返回最后一个元素:

TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("First");
treeSet.add("Last");
assertEquals("Last", treeSet.last());

TreeSet subSet

此方法将返回从 fromElement 到 toElemen t的元素。

请注意:fromElement是包含的,toElement 是不包含的:

SortedSet<Integer> treeSet = new TreeSet<>();
treeSet.add(1);
treeSet.add(2);
treeSet.add(3);
treeSet.add(4);
treeSet.add(5);
treeSet.add(6);
Set<Integer> expectedSet = new TreeSet<>();
expectedSet.add(2);
expectedSet.add(3);
expectedSet.add(4);
expectedSet.add(5);

Set<Integer> subSet = treeSet.subSet(2, 6);

assertEquals(expectedSet, subSet);

TreeSet headSet()

此方法将返回 TreeSet 的元素,这些元素小于指定的元素:

SortedSet<Integer> treeSet = new TreeSet<>();
treeSet.add(1);
treeSet.add(2);
treeSet.add(3);
treeSet.add(4);
treeSet.add(5);
treeSet.add(6);
Set<Integer> subSet = treeSet.headSet(6);

assertEquals(subSet, treeSet.subSet(1, 6));

TreeSet tailSet()

此方法将返回 TreeSet 的元素,这些元素大于或等于指定的元素:

NavigableSet<Integer> treeSet = new TreeSet<>();
treeSet.add(1);
treeSet.add(2);
treeSet.add(3);
treeSet.add(4);
treeSet.add(5);
treeSet.add(6);
Set<Integer> subSet = treeSet.tailSet(3);

assertEquals(subSet, treeSet.subSet(3, true, 6, true));

存储空元素

在Java 7之前,可以将空元素添加到空 TreeSet 中。

但是,这被认为是一个错误。因此,TreeSet 不再支持添加 null。

当我们向 TreeSet 添加元素时,元素将根据其自然顺序或比较器指定的方式进行排序。因此,与现有元素相比,添加 null 会导致 NullPointerException,因为 null 无法与任何值进行比较:

@Test(expected = NullPointerException.class)
public void whenAddingNullToNonEmptyTreeSet_shouldThrowException() {
    Set<String> treeSet = new TreeSet<>();
    treeSet.add("First");
    treeSet.add(null);
}

插入 TreeSet 的元素必须实现 Comparable 接口,或者至少被指定的比较器接受。所有这些元素必须是可相互比较的, 即 e1.compareTo(e2)或 comparator.compare(e1,e2) 不得抛出 ClassCastException。

class Element {
    private Integer id;
// Other methods...
}

Comparator<Element> comparator = (ele1, ele2) -> {undefined
return ele1.getId().compareTo(ele2.getId());
};

@Test
public void whenUsingComparator_shouldSortAndInsertElements() {undefined
Set<Element> treeSet = new TreeSet<>(comparator);
Element ele1 = new Element();
ele1.setId(100);
Element ele2 = new Element();
ele2.setId(200);

treeSet.add(ele1);
treeSet.add(ele2);
 
System.out.println(treeSet);
}

TreeSet 的性能

与 HashSet 相比,TreeSet 的性能更低。操作,比如添加、删除和搜索需要 O(log n)的时间,而像打印操作 ñ 在有序元素需要 O(n)的时间。

局部性原则 - 是根据存储器访问模式,经常访问相同值或相关存储位置的现象的术语。

类似数据通常由具有相似频率的应用程序访问
如果给定排序附近有两个条目,则 TreeSet 将它们放置在数据结构中彼此靠近,因此在内存中
我们可以说一个TreeSet 的数据结构有更大的地方,因此,得出结论:按照局部性原理,我们应该优先考虑一个 TreeSet 的,如果我们短期使用,我们想访问相对靠近元素根据他们的自然顺序相互依赖。

如果需要从硬盘驱动器读取数据(其延迟大于从缓存或内存中读取的数据),则更喜欢 TreeSet,因为它具有更大的局部性

模块 java.base 软件包 java.util
Class TreeSet

java.lang.Object
	java.util.AbstractCollection<E>
		java.util.AbstractSet<E>
			java.util.TreeSet<E>

参数类型

E - 此集维护的元素类型
实现的所有接口

Serializable , Cloneable , Iterable<E> , Collection<E> , NavigableSet<E> , Set<E> , SortedSet<E>

public class TreeSet
extends AbstractSet
implements NavigableSet, Cloneable, Serializable
一个NavigableSet实现基于一个TreeMap 。 的元件使用其有序natural ordering ,或由Comparator集合创建时提供,这取决于所使用的构造方法。
此实现提供了基本的操作(保证的log(n)时间成本add , remove和contains )。

请注意,如果要正确实现Set接口,则由集合维护的排序(无论是否提供显式比较器)必须与equals一致 。 (有关与equals一致的精确定义,请参阅Comparable或Comparator )这是因为Set接口是根据equals操作定义的,但是TreeSet实例使用其compareTo (或compare )方法执行所有元素比较,因此从该集合的角度来看,通过这种方法被认为相等的元素是相等的。 一套的行为是明确的,即使它的排序和equals不一致; 它只是没有遵守Set接口的一般合同。

请注意,此实现不同步。 如果多个线程同时访问树集,并且至少有一个线程修改了该集,则必须在外部进行同步。 这通常通过在自然封装集合的某个对象上进行同步来实现。 如果不存在此类对象,则应使用Collections.synchronizedSortedSet方法“包装”该集合 。 这最好在创建时完成,以防止对集合的意外不同步访问:

SortedSet s = Collections.synchronizedSortedSet(new TreeSet(…));
此类的iterator方法返回的迭代器是快速失败的 :如果在创建迭代器之后的任何时间修改集合,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。

请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。 失败快速迭代器在尽力而为的基础上抛出ConcurrentModificationException 。 因此,编写依赖于此异常的程序以确保其正确性是错误的: 迭代器的快速失败行为应该仅用于检测错误。

此类是 Java Collections Framework 的成员。

TreeSet()	构造一个新的空树集,根据其元素的自然顺序进行排序。
TreeSet(Collection<? extends E> c)	构造一个新的树集,其中包含指定集合中的元素,并根据其元素的 自然顺序进行排序 。
TreeSet(Comparator<? super E> comparator)	构造一个新的空树集,根据指定的比较器进行排序。
TreeSet(SortedSet<E> s)	 构造一个包含相同元素并使用与指定有序集相同排序的新树集。

boolean	add(E e)	如果指定的元素尚不存在,则将其添加到此集合中。
boolean	addAll(Collection<? extends E> c)	将指定集合中的所有元素添加到此集合中。
E	pollFirst()	检索并删除第一个(最低)元素,如果此组为空,则返回 null 。
E	pollLast()	检索并删除最后一个(最高)元素,如果此集合为空,则返回 null 。
boolean remove(Object o)	如果存在,则从该集合中移除指定的元素。
void	clear()	从该集中删除所有元素。
	
E	first()	返回此集合中当前的第一个(最低)元素。
E	last()	返回此集合中当前的最后一个(最高)元素。
E	lower(E e)	返回此集合中的最大元素严格小于给定元素,如果没有这样的元素,则 null 。	
E	higher(E e)	返回此集合中的最小元素严格大于给定元素,如果没有这样的元素,则 null 。
E	floor(E e) 返回此 set 中小于或等于给定元素的最大元素,如果没有这样的元素,则 null 。
E	ceiling(E e)	返回此 set 中大于或等于给定元素的 null 元素,如果没有这样的元素,则 null 。

int size()	返回此集合中的元素数(基数)。
boolean contains(Object o)	如果此set包含指定的元素,则返回 true 。
boolean isEmpty()	如果此集合不包含任何元素,则返回 true 。

Object	clone() 返回此 TreeSet实例的浅表副本。

Iterator<E>	iterator()	以升序返回此集合中元素的迭代器。
Iterator<E>	descendingIterator()	以降序返回此集合中元素的迭代器。

NavigableSet<E> descendingSet()	返回此set中包含的元素的逆序视图。
SortedSet<E>	headSet(E toElement)	 返回此 set 的部分视图,其元素严格小于 toElement 。
NavigableSet<E> headSet(E toElement, boolean inclusive)	返回此 set 的部分视图,其元素小于(或等于,如果 inclusive为true) toElement 。
Spliterator<E>	spliterator()	在此集合中的元素上创建late-binding和故障快速 Spliterator 。
NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)	返回此set的部分视图,其元素范围为 fromElement到 toElement 。
SortedSet<E>	subSet(E fromElement, E toElement)	返回此set的部分视图,其元素范围从 fromElement (含)到 toElement (独占)。
SortedSet<E>	tailSet(E fromElement) 返回此 set 的部分视图,其元素大于或等于 fromElement 。
NavigableSet<E> tailSet(E fromElement, boolean inclusive)	返回此set的部分视图,其元素大于(或等于,如果 inclusive为true) fromElement 。

声明方法的类 java.util.AbstractSet
equals, hashCode, removeAll
声明方法的类 java.util.AbstractCollection
containsAll, retainAll, toArray, toArray, toString
声明方法的类 java.lang.Object
finalize, getClass, notify, notifyAll, wait, wait, wait
声明方法的接口 java.util.Collection
parallelStream, removeIf, stream, toArray
声明方法的接口 java.lang.Iterable
forEach
声明方法的接口 java.util.Set
containsAll, equals, hashCode, removeAll, retainAll, toArray, toArray
声明方法的接口 java.util.SortedSet
comparator