【Simple Java】HashSet vs TreeSet vs LinkedHashSet

使用Set集合的主要原因是因为Set集合里面没有重复的元素。Set集合有三个常见的实现类:HashSet,TreeSet,LinkedHashSet。什么时候,选择哪一个使用非常重要。简单的说,如果你关注性能,应该使用HashSet;如果你需要一个有序的Set集合,应该使用TreeSet;如果你需要一个Set集合保存了原始的元素插入顺序,应该使用LinkedHashSet。

Set接口

Set接口继承Collection接口。Set集合不允许里面存在重复元素,每个元素都必须是唯一的。你只需要往Set集合简单的添加元素,重复元素会被自动移除。

HashSet,TreeSet,LinkedHashSet对比

HashSet是基于散列表实现的,元素没有顺序;add、remove、contains方法的时间复杂度为O(1)。

TreeSet是基于树实现的(红黑树),元素是有序的;add、remove、contains方法的时间复杂度为O(log (n))。因为元素是有序的,它提供了若干个相关方法如first(), last(), headSet(), tailSet()等;

LinkedHashSet介于HashSet和TreeSet之间,是基于哈希表和链表实现的,支持元素的插入顺序;基本方法的时间复杂度为O(1);

TreeSet例子

TreeSet<Integer> tree = new TreeSet<Integer>();
tree.add(12);
tree.add(63);
tree.add(34);
tree.add(45);
Iterator<Integer> iterator = tree.iterator();
System.out.print("Tree set data: ");
while (iterator.hasNext()) {
    System.out.print(iterator.next() + " ");
}

结果输出:

Tree set data: 12 34 45 63

现在,我们换个元素类型,在进行插入,首先定义一个Dog类,如下

class Dog {
    int size;

    public Dog(int s) {
        size = s;
    }

    public String toString() {
        return size + "";
    }
}

然后,往TreeSet添加若干个Dog对象,如下:

public class Q17 {

    public static void main(String[] args) {
        TreeSet<Dog> dset = new TreeSet<Dog>();
        dset.add(new Dog(2));
        dset.add(new Dog(1));
        dset.add(new Dog(3));
        Iterator<Dog> iterator = dset.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
    }

}

以上代码,编译OK,但是运行时报错,如下:

Exception in thread "main" java.lang.ClassCastException: simplejava.Dog cannot be cast to java.lang.Comparable
    at java.util.TreeMap.compare(TreeMap.java:1188)
    at java.util.TreeMap.put(TreeMap.java:531)
    at java.util.TreeSet.add(TreeSet.java:255)
    at simplejava.Q17.main(Q17.java:22)

为什么呢?因为TreeSet是有序的,Dog类需要实现java.lang.Comparable接口的compareTo(),如下:

class Dog implements Comparable<Dog>{
    int size;

    public Dog(int s) {
        size = s;
    }

    public String toString() {
        return size + "";
    }

    @Override
    public int compareTo(Dog o) {
    return size - o.size;
    }
}

结果输出:

1 2 3

HashSet例子

        HashSet<Dog> dset = new HashSet<Dog>();
        dset.add(new Dog(2));
        dset.add(new Dog(1));
        dset.add(new Dog(3));
        dset.add(new Dog(5));
        dset.add(new Dog(4));
        Iterator<Dog> iterator = dset.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }

结果输出:

5 3 2 1 4

注意顺序是不确定的。

LinkedHashSet例子

        LinkedHashSet<Dog> dset = new LinkedHashSet<Dog>();
        dset.add(new Dog(2));
        dset.add(new Dog(1));
        dset.add(new Dog(3));
        dset.add(new Dog(5));
        dset.add(new Dog(4));
        Iterator<Dog> iterator = dset.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }

结果输出如下,保存了插入顺序:

2 1 3 5 4

性能测试

以下代码测试了这三个类add方法的性能:

        Random r = new Random();
        HashSet<Dog> hashSet = new HashSet<Dog>();
        TreeSet<Dog> treeSet = new TreeSet<Dog>();
        LinkedHashSet<Dog> linkedSet = new LinkedHashSet<Dog>();
        // start time
        long startTime = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            int x = r.nextInt(1000 - 10) + 10;
            hashSet.add(new Dog(x));
        }
        // end time

        long endTime = System.nanoTime();
        long duration = endTime - startTime;
        System.out.println("HashSet: " + duration);
        // start time
        startTime = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            int x = r.nextInt(1000 - 10) + 10;
            treeSet.add(new Dog(x));
        }
        // end time
        endTime = System.nanoTime();
        duration = endTime - startTime;
        System.out.println("TreeSet: " + duration);
        // start time
        startTime = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            int x = r.nextInt(1000 - 10) + 10;
            linkedSet.add(new Dog(x));
        }
        // end time
        endTime = System.nanoTime();
        duration = endTime - startTime;
        System.out.println("LinkedHashSet: " + duration);

结果如下,我们可以发现,HashSet性能最好(注:以上代码我自己本地测试,HashSet不一定比LinkedHashSet快...)

HashSet: 2244768
TreeSet: 3549314
LinkedHashSet: 2263320

这个测试并不是很精准,但是基本可以反映出TreeSet是性能最差的,因为需要排序。

相关阅读:ArrayList vs. LinkedList vs. Vector

译文链接:http://www.programcreek.com/2013/03/hashset-vs-treeset-vs-linkedhashset/

时间: 2024-11-07 19:08:54

【Simple Java】HashSet vs TreeSet vs LinkedHashSet的相关文章

Java集合 -- HashSet 与TreeSet和LinkedHashSet的区别

原文:https://www.cnblogs.com/wl0000-03/p/6019627.html Set接口  Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false. Set判断两个对象相同不是使用==运算符,而是根据equals方法.也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象. HashSet与TreeSet都是基于Set接口的实现类.其中TreeSet是Set的子接口SortedSet的实现类.Set接口

【Simple Java】HashMap vs TreeMap vs Hashtable vs LinkedHashMap

Map是一个重要的数据结构,本篇文章将介绍如何使用不同的Map,如HashMap,TreeMap,HashTable和LinkedHashMap. Map概览 Java中有四种常见的Map实现,HashMap,TreeMap,HashTable和LinkedHashMap,我们可以使用一句话来描述各个Map,如下: HashMap:基于散列表实现,是无序的: TreeMap:基于红黑树实现,按Key排序: LinkedHashMap:保存了插入顺序: Hashtable:是同步的,与HashMa

【Simple Java】怎样高效判断数组中是否包含某个特定值

怎样判断一个无序数组是否包含某个特定值?这在JAVA中是一个非常实用的操作,在Stack Overflow问答网站中也同样是一个热门问题: 要完成这个判断,可以通过若干种不同的方式实现,每种实现方式对应的时间复杂读会有很大的不同: 接下来我将展示不同实现方式的时间开销. 四种不同方式检查数组是否包含某个值 使用List: public static boolean useList(String[] arr, String targetValue) { return Arrays.asList(a

【Simple Java】HashMap常用方法

当需要对元素进行计数时,HashMap非常有用,如下例子,统计一个字符串中每个字符出现的次数: package simplejava; import java.util.HashMap; import java.util.Map.Entry; public class Q12 { public static void main(String[] args) { HashMap<Integer, Integer> countMap = new HashMap<Integer, Intege

【Simple Java】Java中怎样创建线程安全的方法

面试问题: 下面的方法是否线程安全?怎样让它成为线程安全的方法? class MyCounter { private static int counter = 0; public static int getCount() { return counter++; } } 本篇文章将解释一个常见的面试题,该问题被谷歌和很多其它公司问起过.它涉及的相对比较初级,而不是关于怎样去设计复杂的并发程序. 首先,这个问题的答案是No,因为counter++操作不是一个原子操作,而是由多个原子操作组成. 举个

【Simple Java】Java字符串中常见的10个问题

下面是Java中10个最常见的关于字符串的问题. 怎样比较字符串?使用==还是equals() 简单的说,“==”用于判断引用是否相等,equals()用于判断值是否相等.除非你要比较两个字符串是否是同一个对象,否则你应该使用equals()方法.如果你知道字符串驻留的概念会更好. 对于敏感信息优先使用字符数组而不是字符串 字符串是不可变的,意味着一旦被创建,他们就会一直存在直到垃圾回收器回收它们.然而对于一个数组来说,你可以明确的改变它们的元素.使用这种方法,敏感信息(如密码)就不会长期存在于

【Simple Java】Java中的内部接口

什么是内部接口 内部接口也称为嵌套接口,即在一个接口内部定义另一个接口.举个例子,Entry接口定义在Map接口里面,如下代码: public interface Map { interface Entry{ int getKey(); } void clear(); } 为什么要使用内部接口 如下是一些有一些强有力的理由: 一种对那些在同一个地方使用的接口进行逻辑上分组: 封装思想的体现: 嵌套接口可以增强代码的易读性和可维护性: 在Java标准库中使用内部接口的一个例子是java.util.

【Simple Java】Java是如何处理别名(aliasing)的

什么是Java别名(aliasing) 别名意味着有多个别名指向同一个位置,且这些别名有不同的类型. 在下面的代码例子中,a和b是两个不同的名字,有不同的类型A和B,B继承A B[] b = new B[10]; A[] a = b; a[0] = new A(); b[0].methodParent(); 在内存中,它们指向了同一个位置,如下: 内存中的该位置同时被a和b指向,在运行期间,方法的调用由实际存储的对象来决定. Java是如何处理别名问题的 如果你拷贝以下代码到你的浏览器,会发现没

【Simple Java】面试问题-使用Java线程做数学运算

这是一个展示如何使用join()方法的例子. 问题: 使用Java多线程计算表达式1*2/(1+2)的值. 解决方案: 使用一个线程做加法运算,另一个线程做乘法运算,还有一个主线程main做除法运算.由于线程之间不需要通讯,所以我们只需要考虑线程的执行顺序. 在main线程中,我们让加法运算线程和乘法运算线程join到主线程,join()方法的作用是使main方法等待,直到调用join的线程执行完毕.在这个例子中,我们希望加法运算线程和乘法运算线程先结束,然后在计算除法运算. package s