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

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介:

一、Set回顾

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

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

java-collection-hierarchy

 

二、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, HashSetObj@2, EuqalsObj@1471cb25, EuqalsObj@3acff49f]

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

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接口,性能较差

 

 

相关文章
|
3月前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
57 6
|
3月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
50 3
|
3月前
|
存储 算法 Java
Java HashSet:底层工作原理与实现机制
本文介绍了Java中HashSet的工作原理,包括其基于HashMap实现的底层机制。通过示例代码展示了HashSet如何添加元素,并解析了add方法的具体过程,包括计算hash值、处理碰撞及扩容机制。
|
3月前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
66 0
[Java]泛型
|
3月前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其独特的“不重复性”要求,彻底改变了处理唯一性约束数据的方式。
【10月更文挑战第14天】从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其独特的“不重复性”要求,彻底改变了处理唯一性约束数据的方式。本文深入探讨Set的核心理念,并通过示例代码展示了HashSet和TreeSet的特点和应用场景。
25 2
|
3月前
|
存储 Java 开发者
HashSet和TreeSet教你重新认识Java集合的无序与有序
【10月更文挑战第14天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了它们分别实现无序和有序存储的机制。通过理解HashSet基于哈希表的无序特性和TreeSet利用红黑树实现的有序性,帮助开发者更好地选择合适的集合类型以满足不同的应用场景。
35 2
|
3月前
|
存储 Java
Java集合框架中的HashSet和TreeSet,解释了它们如何分别实现无序和有序存储。
【10月更文挑战第13天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解释了它们如何分别实现无序和有序存储。通过解析内部机制和示例代码,帮助读者理解这两种集合的特点和应用场景,从而更好地选择合适的集合类型满足实际需求。
36 3
|
27天前
|
监控 NoSQL 时序数据库
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
196 77
|
1月前
|
监控 Docker 容器
在Docker容器中运行打包好的应用程序
在Docker容器中运行打包好的应用程序
|
8天前
|
Ubuntu Linux 开发工具
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
Docker 是一个开源的容器化平台,允许开发者将应用程序及其依赖项打包成标准化单元(容器),确保在任何支持 Docker 的操作系统上一致运行。容器共享主机内核,提供轻量级、高效的执行环境。本文介绍如何在 Ubuntu 上安装 Docker,并通过简单步骤验证安装成功。后续文章将探讨使用 Docker 部署开源项目。优雅草央千澈 源、安装 Docker 包、验证安装 - 适用场景:开发、测试、生产环境 通过以上步骤,您可以在 Ubuntu 系统上成功安装并运行 Docker,为后续的应用部署打下基础。
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈