java基础:13.4 集合框架 - Set (HashSet、LinkedHashSet、TreeSet)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/L20902/article/details/88855527


Set是一种没有重复元素的集合,它所有的方法都是直接继承自Collection接口,并且添加了一个对重复元素的限制.Set要求强化了equals和hashCode两个方法,以使Set集合可以对元素进行排序和对比.
在这里插入图片描述
Java 合集框架中的所有具体类都至少有两个构造方法
一个是创建空合集的无参构造方法, eg:TreeSet treeset = new TreeSet<>();
另一个是用某个合集来创建实例的构造方法。 eg:TreeSet treeset = new TreeSet<>(set1);
 
如果不需要维护元素被插入的顺序,使用 – > HashSet ,因为在散列集中插入和删除元素所花的时间比较少。
如果想元素保存顺序和插入顺序相关,使用LinkedHashSet 。
要强加一个不同的顺序(例如,升序或降序),使用TreeSet 类。
(当需要一个排好序的集合时,可以从散列集创建一个树形集)

 

1、集合set的概念、散列码

1.1基本概念
可以使用集合的三个具体类 散列类 - HashSet链式散列类 - Li nkedHashSet树形集 - TreeSet来创建集合
Set 接口扩展了Collection 接口,集合没有重复元素!!
 

1.2 散列码hashcode
集合中没有重复的对象,当添加一个新对象到集合时(put),JVM如何判断是否有重复??
此处需要了解HashCode的概念和作用了~~~

散列码是由对象导出的一个整数值。在Object中有一个hashCode方法来得到散列码。基本上,每一个对象都有一个默认的散列码,其值就是对象的内存地址。但也有一些对象的散列码不同,比如String对象,它的散列码是对内容的计算结果:
//String对象的散列码计算
String str=“hello”;
int hash=0;
for(int i=0;i<length();i++)
hash=31*hash+charAt(i);

 

2、子接口

Set中没有新添方法,而是在子接口SortedSet和NavigableSet中拓展了一些功能

修饰符和返回值 方法名 描述
端点操作
E first() 返回当前集合第一个元素(低位),没有时抛出异常
E last() 返回当前集合最后一个元素(高位),没有时抛出异常
视图选取
SortedSet subSet(E,E) 返回指定两元素间元素组成的集合
SortedSet headSet(E) 返回指定元素之前元素组成的集合,不包含指定元素
SortedSet tailSet(E) 返回指定元素之后元素组成的集合,包含指定元素
排序器
Comparator<? extend E> comparator() 返回排序器

SortedSet内的元素以自然排序方式维持升序排序,或者依照指定的排序器排序,SortedSet集合相比Set添加了以下对元素操作的方式

  • 视图–允许从SortedSet截取并返回任意范围的元素视图
  • 端点操作—可以直接获取集合头或尾的元素
  • 排序器—返回用于排列元素的排序器

需要格外注意的是,SortedSet视图的端点指向的是存储元素的内存空间,而不是给定的端点元素,视图仅仅是一个查看原集合的窗口,因此任何对视图的操作都会影响原集合,反之亦然.SortedSet在选取视图的时候,需要给定视图的截取的端点,并且含头不含尾,如果想要一个闭区间,同时包含两端点,可以在尾端点后加”/0”(空白字符),这样按照自然排序,前面一个字符自然就是我们给定的尾端点元素.
在这里插入图片描述
 NavigableSet接口继承自SortedSet,视图操作上相比SortedSet,NavigableSet不仅多了一个decendingSet()获取反相排序的集合,而且subSet,headSet,tailSet还多了一个boolean类型参数,这个参数决定返回集合视图中是否包含给定的元素.NavigableSet还有一系列的导航方法,可以更具给定对象在集合内向前或向后寻找满足条件的元素

3、实现

Set接口的实现分为通用实现和专用实现

1.通用实现

通用实现类主要有三个HashSet , LinkedHashSetTreeSet .

HashSet通过哈希表存储元素,它是Set通用类中性能最好的一个,但不保证元素的排序.

TreeSet以红黑树结构存储数据,它的元素按一定规则排序,所以他的性能要比HashSet差许多.

LinkedHashSet在HashSet的基础上,增添了一个链表结构,来保证数据的按插入先后存储有序,因为需要维持一个链表,所以它的性能比HashSet稍微差一点,介于HashSet和TreeSet之间.

HashSet的性能开销在集合内元素数和集合容量上都是线性的,因此HashSet初始化太大会浪费空间和时间,太小的话,在扩容的时候数据结构的拷贝浪费很多时间,如果不指定初始化大小,集合容量默认是16。过去指定一个初始化大小有一定好处,但现在不再是这样了。HashSet还有一个被称为负载系数的调优参数,但一般都是使用默认值,如果不设定负载系数的话,我们最好将初始化大小定义为两倍我们需要的值,即使用不到这么多,一般也不是什么大问题。

2.专用实现

专用实现类主要有两个,EnumSet和CopyOnWriteArraySet.

EnumSet是一个高性能的枚举类型的Set实现类,其内部元素必须都是相同的枚举类型.

CopyOnWriteArraySet是一个支持COW(copy-on-write)机制的集合.CopyOnWriteArraySet对集合的任何修改操作如,add,remove,set时,都会先复制一份,所以在CopyOnWriteArraySet可以安全的并发进行迭代和元素插入删除操作,不需要同步锁,实现了读写分离,但是读操作不具备实时性.CopyOnWriteArraySet只适用集合频繁迭代但很少修改的情景.

 
 

4、HashSet 概念

没有按照元素的插入顺序排列 ( 要强加给它们一个顺序,就需要使用LinkedHashSet 类 )
允许包含值为null的元素,但最多只能一个。

HashSet是作为Mapkey 而存在的,
value 是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。

HashSet<String> names = new HashSet<String>();
name.add("xx");
当遍历打印name时,会发现打印出来的顺序和我们添加的不一样!!

 

当元素个数超过了容量与负载系数的乘积,容量就会向动翻倍
比较高的负载系数会降低空间开销,但是会增加查找时间。通常情况下,默认的负载系数是0.75 ,它是在时间开销和空间
开销上一个很好的权衡。
 

HashSet 作用:
为了提高查找效率!!!
 

HashSet 遍历

Collection 接口继承Iterable 接口, 因此集合中的元素是可遍历的,所有定义在Collection 中的方法都可以用在集合上。
Set 不提供get()来获取指定位置的元素
所以遍历需要用到迭代器,或者增强型for循环

        //遍历Set可以采用迭代器iterator
        for (Iterator<Integer> iterator = numbers.iterator(); iterator.hasNext();) {
            Integer i = (Integer) iterator.next();
            System.out.println(i);
        }
         
        //或者采用增强型for循环
        for (Integer i : numbers) {
            System.out.println(i);
        }

hashCode方法必须与equals方法必须兼容

如果我们自己定义了一个类,想对这个类的大量对象组织成散列表结构便于查找。有一点一定要注意:就是hashCode方法必须与equals方法向兼容。

//hashCode与equals方法的兼容   
public class Employee{   
       public int id;   
       public String name="";   
       //相同id对象具有相同散列码   
       public int hashCode(){    
              return id;   
       }   
       //equals必须比较id   
        public boolean equals(Employee x){   
              if(this.id==x.id) return true;   
              else return false;   
       }   
}  

为什么要这样,因为HashSet不允许相同元素(equals==ture)同时存在在结构中。假如employeeX(1111,“张三”)和employee(1111,“李四”),而Employee.equals比较的是name。这样的话,employeeX和employeeY的equals不相等。它们会根据相同的散列码1111加入到同一个散列单元所指向的列表中。这种情况多了,链表的数据将很庞大,散列冲突将非常严重,查找效率会大幅度的降低。
 
 

5、LinkedHashSet

LinkedHashSet用一个链表实现来扩展HashSet 类,它支持对集合内的元素排序。

HashSet 中的元素是没有被排序的,而LinkedHashSet 中的元素可以按照它们插入集合的顺序提取。

如果不需要维护元素被插入的顺序,就应该使用HashSet ,它会比LinkedHashSet更加高效
LinkedHashSet 保持了元素插入时的顺序。要强加一个不同的顺序(例如,升序或降序),可以使用TreeSet 类。
 
 

6、TreeSet

要强加一个不同的顺序(例如,升序或降序),可以使用 treeSet 类。

SortedSet 是Set 的一个子接口,它可以确保集合中的元素是有序的。

first()last() 返回集合中的第一个元素和最后一个元素

headSet(toElement)tailSet(fromElement) 以分别返回集合中元素小于toElement 和大于或等于fromElement 的那一部分。

NavigableSet 扩展了SortedSet , 并提供方法:

lower(e)、 返回小于一个给定元素的元素。如果没有这样的元素,方法就返回null
floor(e)、返回小于或等于一个给定元素的元素。如果没有这样的元素,方法就返回null
ceiling(e)和 返回大于或等于一个给定元素的元素。如果没有这样的元素,方法就返回null
higher(e) 返回大于一个给定元素的元素。如果没有这样的元素,方法就返回null

pollFirst() 删除和返回树的第一个元素。
pollLast() 删除和返回树的最后一个元素。

treeSet 实现了 SortedSet 接口。

		Set<String> set1 = new HashSet<>(); //本例创建了一个由字符串填充的散列集,然后创建一个由相同字符串构成的树形集
		set1.add("london");
		set1.add("china");
		set1.add("paris");
		
		System.out.println(set1);
		
		TreeSet<String> treeset = new TreeSet<>(set1);
		System.out.println("Sorted tree set:" + treeset);
		System.out.println("first():" + treeset.first());

猜你喜欢

转载自blog.csdn.net/L20902/article/details/88855527