疯狂Java学习笔记(36)-----------Set集合

Set集合 类似于一个罐子,丢进"Set",集合里的过个对象之间没有明显的顺序。

Set集合与Collection集合基本上完全一样,他没有提供任何额外的方法。

实际上Set就是Collection,只是行为略有不同,Set不允许包含重复元素。允许包含值为null的元素,但最多只能有一个null元素。

常用方法

按照定义,Set 接口继承 Collection 接口,所有原始方法都是现成的,没有引入新方法。具体的 Set 实现类依赖添加的对象的 equals() 方法来检查等同性。

各个方法的作用描述:

public int size() :返回set中元素的数目,如果set包含的元素数大于Integer.MAX_VALUE,返回Integer.MAX_VALUE;

public boolean isEmpty() :如果set中不含元素,返回true  ;

public boolean contains(Object o) :如果set包含指定元素,返回true  ;

public Iterator iterator() : 返回set中元素的迭代器,元素返回没有特定的顺序,除非set提高该保证的某些类的实例  ;

public boolean add(Object o) :如果set中不存在指定元素,则向set加入 ;

public boolean remove(Object o) :如果set中存在指定元素,则从set中删除  ;

public boolean removeAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素  ;

public void clear() :从set中删除所有元素;

原理分析

HashSet的元素存放顺序和添加进去时候的顺序没有任何关系;而LinkedHashSet 则保持元素的添加顺序;TreeSet则是对我们的Set中的元素进行排序存放。

一般来说,当要从集合中以有序的方式抽取元素时,TreeSet 实现就会有用处。为了能顺利进行,添加TreeSet  的元素必须是可排序的。 而同样需要对添加到TreeSet中的类对象实现 Comparable  接口的支持。

对于Comparable接口的实现。假定一棵树知道如何保持 java.lang  包装程序器类元素的有序状态。一般说来,先把元素添加到 HashSet,再把集合转换为 TreeSet  来进行有序遍历会更快。这点和HashMap的使用非常的类似。

其实Set的实现原理是基于Map上面的。Set中很多实现类和Map中的一些实现类的使用上非常的相似。Map中的“键值对”,其中的 “键”是不能重复的。这个和Set中的元素不能重复一致,其实Set利用的就是Map中“键”不能重复的特性来实现的。

HashSet的巧妙实现:就是建立一个“键值对”,“键”就是我们要存入的对象,“值”则是一个常量。这样可以确保, 我们所需要的存储的信息之是“键”。而“键”在Map中是不能重复的,这就保证了我们存入Set中的所有的元素都不重复。而判断是否添加元素成功,则是通 过判断我们向Map中存入的“键值对”是否已经存在,如果存在的话,那么返回值肯定是常量:PRESENT  ,表示添加失败。如果不存在,返回值就为null 表示添加成功。

下面是一个测试类

SetTest.java

package com.haixu.set;

import java.util.HashSet;
import java.util.Set;
/*
 * Set 集合测试!
 * */
public class SetTest {
 public static void main(String[] args) {
  //HashSet是Set的典型实现
 Set<String> str1 = new HashSet<String>();

 //添加一个字符串
 str1.add(new String("you will succcess!"));

 //再次添加一个字符串对象
 //因为两个字符串对象通过equals方法比较相等
 //添加失败将返回false
 boolean result = str1.add(new String("Hold on Haixu!"));
 System.out.println(result +"------->" + str1);

 }

}

HashSet类:

1、HashSet不能重复存储equals相同的数据 。原因就是equals相同,数据的散列码也就相同(hashCode必须和equals兼容)。大量相同的数据将存放在同一个散列单元所指向的链表中,造成严重的散列冲突,对查找效率是灾难性的。

2、HashSet的存储是无序的 ,没有前后关系,他并不是线性结构的集合。

3、hashCode必须和equals必须兼容

当向HashSet集合中存入一个元素师,HsahSet会调用对象的hashCode方法来得到对象的hashCode值,然后根据该HashCode值决定该对象的HashSet的位置。

如果两个元素通过equals()方法比较返回true,但他们的哈市COde方法返回值不相等,HashSet将会把他们存放在不同的位置!

这张图片价值不菲呀!好好阅读呀!

在网上找了好多的资料都没有仔细详细的讲解HashSet 类,只能总计总结了!

HashSetTest.java

package com.haixu.set;
/*
 * HashSet类的练习,测试对象的存储位置
 * */
import java.util.HashSet;

class A{

	public boolean equals(Object obj){
		return true;
	}

}
class B{

	public int hashCode(){
		return 1;
	}

}

class C{

	public int hashCode(){
		return 2;
		}
	public boolean equals(Object obj){
		return true;
	}
}

public class HashSetTest {

	public static void main(String[] args) {
		HashSet<Object> str1 = new HashSet<Object>();
		str1.add(new A());
		str1.add(new A());
		str1.add(new B());
		str1.add(new B());
		str1.add(new C());
		str1.add(new C());
		System.out.println(str1);
	}

}

运行结果“[[email protected], [email protected],[email protected],
[email protected],
[email protected]]

HashSetTest2.java

package com.haixu.set;

import java.util.HashSet;
import java.util.Iterator;

class R{
	int count;
	public R(int count){
		this.count = count;
	}
	public String toString(){
		return "R[count:" + count + "]";
	}
	public boolean equals(Object obj){
		if(this == obj){
			return true;
		}
		if(obj != null && obj.getClass() == R.class ){
			R r = (R)obj;
			if(r.count == this.count){
				return true;
			}
		}
		return false;
	}
	public int hashCode(){
		return this.count;
	}
}
public class HashSetTest2 {

	public static void main(String[] args) {
		HashSet<Object> hs =new HashSet<Object>();
		hs.add(new R(5));
		hs.add(new R(-3));
		hs.add(new R(9));
		hs.add(new R(-2));
		//打印HashSet集合,集合元素没有重复
		System.out.println(hs);
		//取出一个元素
		Iterator it = (Iterator) hs.iterator();
		R first = (R)it.next();
	//为第一个元素count实例变量赋值
		first.count = -3;
		//再次输出HashSet集合,集合没有重复元素
		System.out.println(hs);
		//删除count为-3的R对象
		hs.remove(new R(-3));
		//可以看出被删除类一个对象
		System.out.println(hs);

		System.out.println("hs是否包含count为-3的R对象?" + hs.contains(new R(-3)));
		System.out.println("hs是否包含count为5的R对象?" + hs.contains(new R(5)));

	}

}

TreeSet原理:

   /*

* TreeSet存储对象的时候, 可以排序, 但是需要指定排序的算法

*

* Integer能排序(有默认顺序), String能排序(有默认顺序), 自定义的类存储的时候出现异常(没有顺序)

*

* 如果想把自定义类的对象存入TreeSet进行排序, 那么必须实现Comparable接口

*   在类上implement Comparable

*   重写compareTo()方法

*   在方法内定义比较算法, 根据大小关系, 返回正数负数或零

*   在使用TreeSet存储对象的时候, add()方法内部就会自动调用compareTo()方法进行比较, 根据比较结果使用二叉树形式进行存储

*/

TreeSet是依靠TreeMap来实现的。

TreeSet是一个有序集合,TreeSet中的元素将按照升序排列,缺省是按照自然排序进行排列,意味着TreeSet中的元素要实现Comparable接口。或者有一个自定义的比较器。

我们可以在构造TreeSet对象时,传递实现Comparator接口的比较器对象。

import java.util.Iterator;

import java.util.*;

public class TreeSetTest {

public static void main(String[] args) {

Set ts = new TreeSet();

ts.add("Haixu");

ts.add("success");

ts.add("you can!");

Iterator it = ts.iterator();

while (it.hasNext()) {

System.out.println(it.next());

}

}

}

输出结果“Haixu, success,you can!

打印结果不是和先前加入的顺序一样,它是按照一个字母的排序法进行排序的。这是因为String 类实现了Comparable接口。

如果我们自己定义的一个类的对象要加入到TreeSet当中,那么这个类必须要实现Comparable接口。

   比较器

在使用Arrays对数组中的元素进行排序的时候,可以传递一个比较器。

在使用Collections对集合中的元素进行排序的时候,可以传递一个比较器。

那么在使用TreeSet对加入到其中的元素进行排序的时候可以传入一个比较器吗?

  public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<E,Object>(comparator));
    }

通过查看它的构造方法就知道可以传入一个比较器。

EnumSet 是一个与枚举类型一起使用的专用 Set 实现。枚举set中所有元素都必须来自单个枚举类型(即必须是同类型,且该类型是Enum的子类)。

枚举类型在创建 set 时显式或隐式地指定。枚举 set 在内部表示为位向量。 此表示形式非常紧凑且高效。此类的空间和时间性能应该很好,

足以用作传统上基于 int 的“位标志”的替换形式,具有高品质、类型安全的优势。

如果指定的 collection 也是一个枚举 set,则批量操作(如 containsAll 和 retainAll)也应运行得非常快。

由 iterator 方法返回的迭代器按其自然顺序 遍历这些元素(该顺序是声明枚举常量的顺序)。

返回的迭代器是弱一致的:它从不抛出 ConcurrentModificationException,也不一定显示在迭代进行时发生的任何 set 修改的效果。

不允许使用 null 元素。试图插入 null 元素将抛出 NullPointerException。但是,

试图测试是否出现 null 元素或移除 null 元素将不会抛出异常。

像大多数 collection 一样,EnumSet 是不同步的。如果多个线程同时访问一个枚举 set,

并且至少有一个线程修改该 set,则此枚举 set 在外部应该是同步的。这通常是通过对自然封装该枚举 set 的对象执行同步操作来完成的。

如果不存在这样的对象,则应该使用 Collections.synchronizedSet(java.util.Set) 方法来“包装”该 set。

最好在创建时完成这一操作,以防止意外的非同步访问:

Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(Foo.class));

实现注意事项:所有基本操作都在固定时间内执行。虽然并不保证,但它们很可能比其 HashSet 副本更快。

如果参数是另一个 EnumSet 实例,则诸如 addAll() 和 AbstractSet.removeAll(java.util.Collection) 之类的批量操作

也会在固定时间内执行。

注意1:不允许使用 null 元素。试图插入 null 元素将抛出 NullPointerException。但是,

试图测试是否出现 null 元素或移除 null 元素将不会抛出异常。

注意2:EnumSet是不同步的。不是线程安全的。

注意3:EnumSet的本质就为枚举类型定制的一个Set,且枚举set中所有元素都必须来自单个枚举类型。

注意4:关于EnumSet的存储,文档中提到是这样的。 “枚举 set 在内部表示为位向量。”

我想它应该是用一个bit为来表示的于之对应的枚举变量是否在集合中。

比如:0x1001

假如约定从低位开始,就表示第0个,和第三个枚举类型变量在EnumSet中。

这样的话空间和时间性能也就应该很好。

注意5:至于Enum的枚举常量的位置(序数)可以用Enum的ordinal()方法得到。

注意6:在jdk内部可以一个数组的形式一个枚举的枚举常量。

下面是来自来JDK中RegularEnumSet.java的一个示例

private static <E extends Enum<E>> E[] getUniverse(Class<E> elementType) {

return SharedSecrets.getJavaLangAccess()

.getEnumConstantsShared(elementType);

}

注意7:元素属于哪种枚举类型必须在创建 set 时显式或隐式地指定.

注意8:关于枚举类型的更多知识可参考《枚举类型》

Enumset是个虚类,我们只能通过它提供的静态方法来返回Enumset的实现类的实例。

返回EnumSet的两种不同的实现:如果EnumSet大小小于64,

就返回RegularEnumSet实例(当然它继承自EnumSet),这个EnumSet实际上至用了一个long来存储这个EnumSet。

如果 EnumSet大小大于等于64,则返回JumboEnumSet实例,它使用一个long[]来存储。这样做的好处很明显: 大多数情况下返回的RegularEnumSet效率比JumboEnumSet高很多。

实例1
import java.util.Comparator;
import java.util.EnumSet;
import java.util.Random;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class Test {
 /**
   * @param args
   */
 public static void main(String[] args) {
  System.out.println("EnumSet.noneOf");
  EnumSet<Student> set=EnumSet.noneOf(Student.class);
   set.add(Student.HARRY);
   set.add(Student.ROBBIE);
   set.add(Student.ROBIN);
  for(Student p:set)
   System.out.println(p);
  set.clear();
  System.out.println("EnumSet.allOf");
  set=EnumSet.allOf(Student.class);
  for(Student p:set)
   System.out.println(p);
  set.clear();
  System.out.println("EnumSet.Of one");
  set=EnumSet.of(Student.ROBIN);
  for(Student p:set)
   System.out.println(p);
  System.out.println("EnumSet.Of two");
  set=EnumSet.of(Student.ROBIN,Student.HARRY);
  for(Student p:set)
   System.out.println(p);
 }
}
enum   Student
{
 ROBIN("robin"),
 HARRY("harry",40),
 ROBBIE("robbie");
 String name;
 int age;
 private Student(String name)
 {
  this(name,0);
 }
 private Student(String name,int age)
 {
  this.name=name;
  this.age=age;
 }
 public String toString()
 {
  return name;
 }
} 
时间: 2024-10-11 01:51:20

疯狂Java学习笔记(36)-----------Set集合的相关文章

【疯狂Java学习笔记】【理解面向对象】

[学习笔记]1.Java语言是纯粹的面向对象语言,这体现在Java完全支持面向对象的三大基本特征:封装.继承.多态.抽象也是面向对象的重要组成部分,不过它不是面向对象的特征之一,因为所有的编程语言都需要抽象. 2.面向对象开发方法比较结构化开发方法的优势在于可以提供更好的可重用性.可扩展性.可维护性. 3.基于对象和面向对象的区别:基于对象也使用了对象,但是无法通过现有的对象作为模板来生成新的对象类型,继而产生新的对象,也就是说,基于对象没有继承的特点.而面向对象有继承,而多态则是建立在继承的基

疯狂java学习笔记之面向对象(八) - static和final

一.static: 1.static是一个标识符: - 有static修饰的成员表明该成员是属于类的; - 没有static修饰的成员表明该成员是属于实例/对象的. 2.static修饰的成员(Field.方法.初始化块),与类共存亡:static修饰的成员建议总是通过类名来访问,虽然它也可以通过实例来访问(实质也是通过类来访问的),所以平时若在其他程序中见到通过实例/对象来访问static成员时,可以直接将实例/对象 替换成类名: 3.程序都是先有类再有对象的,有可能出现有类但没有实例/对象的

疯狂Java学习笔记(61)-----------40个Java集合面试问题和答案

1.Java集合框架是什么?说出一些集合框架的优点? 每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector.Stack.HashTable和Array.随着集合的广泛使用,Java1.2提出了囊括所有集合接口.实现和算法的集合框架.在保证线程安全的情况下使用泛型和并发集合类,Java已经经历了很久.它还包括在Java并发包中,阻塞接口以及它们的实现.集合框架的部分优点如下: (1)使用核心集合类降低开发成本,而非实现我们自己的集合类. (2)随着使用经过严格测试的集合框架类,代

疯狂Java学习笔记(37)----------List集合

在网上找了很长时间关于List集合的资料,发现都是代码,理论性的东西太少了,对于想要深入的学习我来说,很困难呀,光看代码,不能解决问题呀!所以,自己精心的搜刮来了一点资料在这整理了一下! List集合! ·List列表 ·list: list代表有序.可重复集合,每个元素都有对应的索引,所以List集合中的元素可以重复.List集合默认暗元素的添加顺序设计元素! ·list当然也用于collection中的所有方法,并且自己也有添加了额外的方法,所有List实现类都可以调用这些方法来操作元素.L

疯狂Java学习笔记(59)-----------50道Java线程面试题

50道Java线程面试题 下面是Java线程相关的热门面试题,你可以用它来好好准备面试. 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速.比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒.Java在语言层面对多线程提供了卓越的支持,它也是一个很好的卖点.欲了解更多详细信息请点击这里. 2) 线程和进程有什么区别? 线程是进程的子集,一个

疯狂Java学习笔记(34)----------Iterator、Collection接口以及foreach

Iterator.Collection接口: 如下图:Iterator.Collection同在一个包中: 红字部分使我们经常遇到的,但是遇到又不知道怎么去理解,去应用它! Collection是最基本集合接口,它定义了一组允许重复的对象.Collection接口派生了两个子接口Set和List,分别定义了两种不同的存储方式,如下: 2. Set接口 Set接口继承于Collection接口,它没有提供额外的方法,但实现了Set接口的集合类中的元素是无序且不可重复. 特征:无序且不可重复. 3.

疯狂Java学习笔记(51)-----------面试题

自己做了一点面试题,感觉很经典,分享给大家,发现还有很多东西需要学! 一.String,StringBuffer, StringBuilder 的区别是什么?String为什么是不可变的? 答:   1.String是字符串常量,StringBuffer和StringBuilder都是字符串变量.后两者的字符内容可变,而前者创建后内容不可变. 2.String不可变是因为在JDK中String类被声明为一个final类. 3.StringBuffer是线程安全的,而StringBuilder是非

疯狂Java学习笔记(87)-----------十篇必读的Java文章

1.Brian Goetz:"管理工作:发人深省的部分" 这其实不是一篇博文,而是Brian Goetz对于甲骨文公司Java的管理的一个非常有趣的讨论的记录.在 以前我们将Java语言与Scala或者Ceylon相比较的时候,对其1-2个特性一直稍微有些意见. 对于为什么Java尽快变得和其他语言一样"时髦"不是一个好主意,Brian提出了很好的观点.每一个Java开发者都应有所了解(大约一个小时). Youtube链接. 2.Aleksey Shipilёv:(

疯狂Java学习笔记(52)-----------Annotation(注释)第一篇

从JDK1.5开始,Java中增加了对元数据(MetaData)的支持,也就是Annotation(注释),这种Annotation与Java程序中的单行注释和文本注释是有一定区别,也有一定联系的.其实,我们现在说的Annotation是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理.通过Annotation,程序开发人员可以在不改变原来逻辑的情况下,在源文件嵌入一些补充的信息.代码分析工具,开发工具和部署工具可通过这些补充信息进行验正或者部署. Annotatio