Java中的HashSet和TreeSet
在讨论集合(Set)的区别时,首先需要考虑的是元素的插入顺序和处理方式。Java中的HashSet是一个实现Set接口的类,支持由哈希表实现的HashMap。该类允许空元素,对于增加、删除、包含和大小等基本操作的性能表现非常快,前提是哈希函数将元素正确分散在桶中。而TreeSet是实现SortedSet接口的类,使用树来存储元素,并保持元素的自然顺序,无论是否提供了显式比较器都可以这样做。
1. 速度和内部实现
对于搜索、插入和删除等操作,HashSet的平均时间复杂度为常量级,性能比TreeSet更快。HashSet是利用哈希表实现的。而TreeSet则需要O(Log n)的时间,可以提供排序的数据,它支持较多的操作,如higher()(返回最小的高层元素)、floor()、ceiling()等,这些操作在TreeSet中的时间复杂度也是O(Log n),但是在HashSet中不支持。TreeSet是使用自平衡二叉查找树(红黑树)实现的,它在Java中由TreeMap进行支持。
2. 排序
HashSet中的元素不是有序的,而TreeSet则根据Java中的Comparable或Comparator方法定义的排序方法来维护对象的顺序。TreeSet默认按升序排列元素,还提供了一些处理有序集合的方法,如first()、last()、headSet()、tailSet()等。
3. 空对象
HashSet允许空对象存在,而TreeSet不允许并会引发NullPointerException异常。因为TreeSet使用compareTo()方法来比较键值,compareTo()方法会抛出java.lang.NullPointerException异常。
4. 比较
HashSet使用equals()方法来比较集合中的两个对象以及检测重复。而TreeSet则使用compareTo()方法来完成相同的目的。如果equals()和compareTo()不一致,即对于两个相等的对象,equals()应该返回true,而compareTo()应该返回零,则会打破Set接口的约定,并允许在Set的实现中(如TreeSet)中存在重复元素。
注意: 如果需要排序的Set,最好先将元素添加到HashSet中,然后再将其转换为TreeSet,而不是创建TreeSet后再向其中添加元素。
程序员们在比较HashSet和TreeSet之后可能会问: 何时应选择TreeSet而不是HashSet呢?
- 需要排序的唯一元素而非唯一元素。由TreeSet给出的排序列表始终是升序的。
- TreeSet的局部性大于HashSet。如果两个条目在顺序中相邻,则TreeSet会将它们放在数据结构和内存中彼此靠近的位置,而HashSet则会将它们分散到内存中,无论它们与哪些键关联。
- TreeSet在底层使用红黑树算法对元素进行排序。当需要频繁进行读/写操作时,TreeSet是一个很好的选择。
- LinkedHashSet是介于这两者之间的另一种数据结构。它提供了与HashSet相似的时间复杂度,并保持插入顺序(注意这不是排序顺序,而是元素插入的顺序)。
实现: 在这里,我们将会提供两个示例,分别讨论HashSet和TreeSet的实现。首先来看HashSet,然后再探讨TreeSet。
- HashSet例子
- TreeSet例子
例1:
输出:
例2:
输出:
现在是时候 实现TreeSet 来更好地理解它。
例1:
输出:
例2:
输出: