Java 容器 & 泛型:三、HashSet,TreeSet 和 LinkedHashSet比较

Writer:BYSocket(泥沙砖瓦浆木匠)

微博:BYSocket

豆瓣:BYSocket

上一篇总结了下ArrayList 、LinkedList和Vector比较,今天泥瓦匠总结下Hash 、LinkedList和Vector比较。其实大家都是Collection,只不过有点各自特性。那就是数据结构的不同表现。

一、Set回顾

一个不包括重复元素(包括可变对象)的Collection,是一种无序的集合。Set不包含满 a.equals(b) 的元素对a和b,并且最多有一个null。
泥瓦匠的记忆宫殿:
1、不允许包含相同元素

2、判断对象是否相同,根据equals方法

二、HashSet

一个按着Hash算法来存储集合中的元素,其元素值可以是NULL。它不能保证元素的排列顺序。同样,HashSet是不同步的,如果需要多线程访问它的话,可以用 Collections.synchronizedSet 方法来包装它:

?


1

Set s = Collections.synchronizedSet(new HashSet(...));

同上一节一样,用迭代器的时候,也要注意 并发修改异常ConcurrentModificationException。

要注意的地方是,HashSet集合判断两个元素相等不单单是equals方法,并且必须hashCode()方法返回值也要相等。看下面的例子:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47


import java.util.HashSet;

class EuqalsObj

{

    
public boolean equals(Object obj)

    
{

        
return true;

    
}

}

class HashCodeObj

{

    
public int hashCode()

    
{

        
return 1;

    
}

}

class HashSetObj

{

    
public int hashCode()

    
{

        
return 2;

    
}

    
public boolean equals(Object obj)

    
{

        
return true;

    
}

}

public class HashSetTest

{

    
public static void main(String[] args)

    
{

        
HashSet objs = new HashSet();

        
objs.add(new EuqalsObj());

        
objs.add(new EuqalsObj());

        
objs.add(new HashCodeObj());

        
objs.add(new HashCodeObj());

        
objs.add(new HashSetObj());

        
objs.add(new HashSetObj());

        

        
System.out.println("HashSet Elements:");

        
System.out.print("\t" + objs + "\n");

    
}

}

Run 一下,控制台如下输出:

?


1

2


HashSet Elements:

    
[HashCodeObj@1 , HashCodeObj@1 , [email protected], [email protected], [email protected]]

泥瓦匠根据结果,一一到来。首先,排列顺序不定。

HashSetObj 类满足我们刚刚的要求,所以集合中只有一个且它的HashCode值为2。

HashCodeObj 类虽然它们HashCode值为1,但是他们不相等。(其实当HashCode值一样,这个存储位置会采用链式结构保存两个HashCodeObj对象。)

同样,EqualsObj 类他们相等,但是他们HashCode值不等,分别为1471cb25、3acff49f。

因此,用HashSet添加可变对象,要注意当对象有可能修改后和其他对象矛盾,这样我们无法从HashSet找到准确我们需要的对象。

三、LinkedHashList

HashSet的子类,也同样有HashCode值来决定元素位置。但是它使用链表维护元素的次序。记住两个字:有序

有序的妙用,复制。比如泥瓦匠实现一个HashSet无序添加,然后复制一个一样次序的HashSet来。代码如下:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33


package com.sedion.bysocket.collection;

import java.util.HashSet;

import java.util.LinkedHashSet;

import java.util.Set;

public class LinkedHashListTest

{

    
public static void main(String[] args)

    
{

        
/* 复制HashSet */

        
Set h1 = new HashSet<
String
>();

        
h1.add("List");

        
h1.add("Queue");

        
h1.add("Set");

        
h1.add("Map");

        

        
System.out.println("HashSet Elements:");

        
System.out.print("\t" + h1 + "\n");

        

        
Set h2 = copy(h1);

        
System.out.println("HashSet Elements After Copy:");

        
System.out.print("\t" + h2 + "\n");

    
}

    

    
@SuppressWarnings({ "rawtypes", "unchecked" })

    
public static Set copy(Set set)

    
{

        
Set setCopy = new LinkedHashSet(set);

        
return setCopy;

    
}

    

}

Run 一下,控制台输出:

?


1

2

3

4


HashSet Elements:

    
[Map, Queue, Set, List]

HashSet Elements After Copy:

    
[Map, Queue, Set, List]

可见,每个数据结构都有它存在的理由。

四、TreeSet

TreeSet使用树结构实现(红黑树),集合中的元素进行排序,但是添加、删除和包含的算法复杂度为O(log(n))。

举个例子吧,首先我们定义一个Bird类。(鸟是泥瓦匠最喜欢的动物)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15


class Bird

{

    
int size;

    

    
public Bird(int s)

    
{

        
size = s;

    
}

    

    
public String toString()

    
{

        
return size + "";

    
}

}

然后用TreeSet添加Bird类。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18


public class TreeSetTest

{

    
public static void main(String[] args)

    
{

        
TreeSet<
Bird
> bSet = new TreeSet<
Bird
>();

        
bSet.add(new Bird(1));

        
bSet.add(new Bird(3));

        
bSet.add(new Bird(2));

        

        
Iterator<
Bird
> iter = bSet.iterator();

        

        
while (iter.hasNext())

        
{

            
Bird bird = (Bird) iter.next();

            
System.out.println(bird);

        
}

    
}

}

Run一下,控制台输出如下:

?


1

2

3

4

5


Exception in thread "main" java.lang.ClassCastException: Bird cannot be cast to java.lang.Comparable

    
at java.util.TreeMap.compare(Unknown Source)

    
at java.util.TreeMap.put(Unknown Source)

    
at java.util.TreeSet.add(Unknown Source)

    
at com.sedion.bysocket.collection.TreeSetTest.main(TreeSetTest.java:29)

答案很明显,TreeSet是排序的。所以Bird需要实现Comparable此接口。

java.lang.Comparable此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法

修改Bird如下:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21


class Bird implements Comparable<
Bird
>

{

    
int size;

    

    
public Bird(int s)

    
{

        
size = s;

    
}

    

    
public String toString()

    
{

        
return size + "号鸟";

    
}

    
@Override

    
public int compareTo(Bird o)

    
{

        
return size - o.size;

    
}

    

}

再次Run一下:

?


1

2

3


1号鸟

2号鸟

3号鸟

五、性能测试比较

针对上面三种Set集合,我们对它们的Add方法进行性能测试:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71


import java.util.HashSet;

import java.util.LinkedHashSet;

import java.util.Random;

import java.util.TreeSet;

class Bird implements Comparable<
Bird
>

{

    
int size;

    

    
public Bird(int s)

    
{

        
size = s;

    
}

    

    
public String toString()

    
{

        
return size + "号鸟";

    
}

    
@Override

    
public int compareTo(Bird o)

    
{

        
return size - o.size;

    
}

    

}

public class Set

{

    
public static void main(String[] args)

    
{

        
Random r = new Random();

         

        
HashSet<
Bird
> hashSet = new HashSet<
Bird
>();

        
TreeSet<
Bird
> treeSet = new TreeSet<
Bird
>();

        
LinkedHashSet<
Bird
> linkedSet = new LinkedHashSet<
Bird
>();

     

        
// start time

        
long startTime = System.nanoTime();

     

        
for (int i = 0; i < 1000; i++) {

            
int x = r.nextInt(1000 - 10) + 10;

            
hashSet.add(new Bird(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 Bird(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 Bird(x));

        
}

        
// end time

        
endTime = System.nanoTime();

        
duration = endTime - startTime;

        
System.out.println("LinkedHashSet: " + duration);

    
}

}

Run一下,可以在控制台中看出:

?


1

2

3


HashSet: 2610998

TreeSet: 3195378

LinkedHashSet: 2673782

可见,TreeSet因为需要进行比较,所以性能比较差。

六、总结

HashSet:equlas hashcode

LinkedHashSet:链式结构

TreeSet:比较,Comparable接口,性能较差

    Java 容器 & 泛型:三、HashSet,TreeSet 和 LinkedHashSet比较

    时间: 2024-08-11 01:05:25

    Java 容器 & 泛型:三、HashSet,TreeSet 和 LinkedHashSet比较的相关文章

    Java 容器 &amp; 泛型:五、HashMap 和 TreeMap的自白

    Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket Java 容器的文章这次应该是最后一篇了:Java 容器 系列. 今天泥瓦匠聊下 Maps. 一.Map回顾 Map,又称映射表,是将键映射到值的对象.有四种实现Map接口并且经常使用的Map集合为:HashMap,TreeMap,Hashtable 和 LinkedHashMap. 泥瓦匠记忆宫殿: 1.一个映射不包含重复的键. 2.每个键最多只能映射到一个值. 二.HashMap HashMap

    Java 容器 & 泛型:一、认识容器

    Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket 容器是Java语言学习中重要的一部分.泥瓦匠我的感觉是刚开始挺难学的,但等你熟悉它,接触多了,也就"顺理成章"地知道了.Java的容器类主要由两个接口派生而出:Collection和Map. 一.Collection vs Collections 首先,Collection 和 Collections 是两个不同的概念.之所以放在一起,是为了更好的比较.Collection是容器层次结构中

    Java 容器 & 泛型:二、ArrayList 、LinkedList和Vector比较

    Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket 继续上一篇的容器文章认识容器,泥瓦匠慢慢带你们走进List的容器解说.今天泥瓦匠想说说 ArrayList .LinkedList和Vector比较. 一.List回顾 序列(List),有序的Collection,正如它的名字一样,是一个有序的元素列表.确切的讲,列表通常允许满足 e1.equals(e2) 的元素对 e1 和 e2,并且如果列表本身允许 null 元素的话,通常它们允许多个 nu

    (转载)Java 容器 &amp; 泛型:三、HashSet,TreeSet 和 LinkedHashSet比较

    上一篇总结了下ArrayList .LinkedList和Vector比较,今天泥瓦匠总结下Hash .LinkedList和Vector比较.其实大家都是Collection,只不过有点各自特性.那就是数据结构的不同表现. 一.Set回顾 一个不包括重复元素(包括可变对象)的Collection,是一种无序的集合.Set不包含满 a.equals(b) 的元素对a和b,并且最多有一个null.泥瓦匠的记忆宫殿:1.不允许包含相同元素 2.判断对象是否相同,根据equals方法 二.HashSe

    (转载)Java 容器 &amp; 泛型:一、认识容器

    容器是Java语言学习中重要的一部分.泥瓦匠我的感觉是刚开始挺难学的,但等你熟悉它,接触多了,也就“顺理成章”地知道了.Java的容器类主要由两个接口派生而出:Collection和Map. 一.Collection vs Collections 首先,Collection 和 Collections 是两个不同的概念.之所以放在一起,是为了更好的比较.Collection是容器层次结构中根接口.而Collections是一个提供一些处理容器类静态方法的类. JDK不提供Collection接口

    HashSet,TreeSet和LinkedHashSet的区别

    Set接口Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false.Set判断两个对象相同不是使用==运算符,而是根据equals方法.也就是说,只要两个对象用equals方法比较返回true,Set就不 会接受这两个对象. HashSet: 顺序是不确定性的. LinkedHashSet:按照输入的顺序进行输出. TreeSet:SortedSet 接口的唯一实现类,保证集合元素处于排序状态. 1.HashSetHashSet有以下特点    不能保证元素的

    Set下面HashSet,TreeSet和LinkedHashSet的区别

    Set接口Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false.Set判断两个对象相同不是使用==运算符,而是根据equals方法.也就是说,只要两个对象用equals方法比较返回true,Set就不 会接受这两个对象. HashSetHashSet有以下特点? 不能保证元素的排列顺序,顺序有可能发生变化? 不是同步的? 集合元素可以是null,但只能放入一个null当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方

    HashSet,TreeSet和LinkedHashSet

    Set接口 Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false. Set判断两个对象相同不是使用==运算符,而是根据equals方法.也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象. HashSetHashSet有以下特点 不能保证元素的排列顺序,顺序有可能发生变化 不是同步的 集合元素可以是null,但只能放入一个null 当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法

    Java 容器 &amp; 泛型:六、容器讲到为什么要使用泛型

    Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket ArrayList是集合类中无处不在的,泛型也是,泛型对集合类尤其有用.但是为啥要使用泛型?理解好了这个问题可以帮助理解相关的更多知识点.下面泥瓦匠以最简单的例子来验证这个问题. 一.泛型 泛型的目的是为了可以让更多不同类型的对象重用.没错,这样理解就太low.真正目的是为了在编译时找到bug,而不是在运行时.(编译时,指的是源代码翻译成机器识别的代码的时候.运行时,是指代码在机器中运行的时候.)泛