Java Set接口

      一、Set接口的层级结构
       1、特点
       2、层级关系
       二、HashSet
      1、数据结构:哈希表
      2、保证元素唯一性
      3、HashSet添加、删除、包含判断依据
      三、TreeSet
      1、TreeSet的介绍
      2、TreeSet存储自定义对象
      3、数据结构:二叉树(红黑树)
      4、比较器
      5、保证元素唯一性
      

一、Lis接口的层级结构

1、特点
Set是无序不可重复的集合。Set集合与Collection基本相似,它没有提供额外的方法,可以说Set就是一个Collection 。

注意: 无序指的是:元素的存入和取出的顺序不一定一致

2、层级结构

Collection
    |-- Set : 元素时无序的,元素不可以重复。 
       |-- HashSet: 底层数据结构是哈希表,线程是非同步的。
       | --TreeSet:  底层数据结构是二叉树,可以对Set集合中的元素进行排序

二、HashSet
1、数据结构: 哈希表
Hash是一种数据结构,用户查找对象。Hash为每一个对象计算出一个整数,称为Hash Code(哈希值)。哈希表是按照哈希值来存的。当我们添加元素时,哈希值是一样的,这时候,会进行是否是同一对象判断equals,如果不是同一对象,那么会在当前对象下顺延(串下来的)。
哈希表是一个链接式列表的阵列。每一个列表称为一个buckets(哈希表元)。对象位置的计算index=HashCode%buckets。

如何查看哈希值?

Demo d1 = new Demo();
System.out.println(obj); 
输出: Demo@15db9742 

为什么是输出Demo@15db9742这样的格式?
我们看下Object中的public String toString()方法的实现
Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:

getClass().getName() + '@' + Integer.toHexString(hashCode())

所以,15db9742 就是哈希值。

1、无序不重复

	public static void main(String[] args) {
		
		HashSet hs = new HashSet();
		sop(hs.add("java1"));  // true
		sop(hs.add("java1")); //false
		hs.add("java2");
		
		Iterator it = hs.iterator();
		while(it.hasNext()){
			sop(it.next());
		}
	}
	
	public static void sop(Object obj){
		System.out.println(obj);
	}

运行结果

true
false
java2
java1     
哈希值一样,同一对象。添加失败。存入和取出顺序不一致。

2.保证元素唯一性
HashSet是如何保证元素的唯一性呢?
是通过元素的两个方法,hashCode和equals方法来完成 的。
先计算哈希值。
( 1 ) 如果元素的hashCode值不同,不会调用equals,添加成功
( 2 ) 如果元素的hashCode值相同,才会判断equals是否为true,为true添加失败
所以,当我们自定义对象的时候,一般要复写hashcode和equals方法,因为自定义对象可能要存放到hashSet集合中。还有,复写hashCode要尽量保证哈希值的唯一性,一般根据判断条件生成
注意: 复写的原则。
下面看一例子: hashSet存自定义对象Person,当姓名和年龄一致,元素重复。

	public static void main(String[] args) {
	    HashSet hs = new HashSet();
		hs.add(new Person("a1", 11));
		hs.add(new Person("a2", 22));
		hs.add(new Person("a3", 33));
		hs.add(new Person("a3", 33));
		Iterator it = hs.iterator();
		while(it.hasNext()){
			Personperson = (Person)it.next();
			sop(person.getName()+"::"+person.getAge());
		}
		
		//重写了equals,没重写hashcode方法。发现equals发现没被调用,而且都存入成功。说明计算得到是不同的哈希值。
		// 鉴于需求,我们需要去重。说明,我们需要覆盖hashcode方法,建立自己的哈希值。
		// 哈希值的生成根据判断条件
		// 重写hashcode方法,发现,去重成功了。而且equals方法运行了。
}
class Person{
	private String name;
	private int age;
	public Person(String n,int a){
		this.name = n;
		this.age = a;
	}
	
	public String getName(){
		return this.name;
	}
	
	public int getAge(){
		return this.age;
	}
	
	public boolean equals(Object obj){
		if(!(obj instanceof Person))
			return false;
		Personp = (Person)obj;
		System.out.println(this.name+"...."+this.age);
		return this.name.equals(p.getName()) && this.age == p.getAge();
	}
	
	public int hashCode()
	{
		System.out.println(this.name+"...hashcode");
		//return 60; 会有很多重复比较
		return this.name.hashCode()+this.age*13;
	}

3、HashSet添加、删除、包含判断依据
添加前面说了。

hs.remove(new Person("a1", 11));
hs.contains(new Person("a2", 12));

删除:先计算对象new Person("a1", 11)的哈希值,如果哈希值不存在,删除失败。如果哈希值存在,调用equals方法,找到对象并删除对象。
包含 :先计算对象的哈希值。如果哈希值存在,调用equals方法,返回结果。如果不存在,直接返回false。

总结 :HashSet对于判断元素是否存在以及删除等操作依赖的方法是元素的hashcode和equals方法。同理,ArrayList判断元素是否存在以及删除等操作,只依赖元素的equals方法。原因其实也很简单,跟数据结构有关。数据结构不同,依赖的方法不一样。

三、TreeSet

1、TreeSet的介绍
TreeSet是SortedSet接口的唯一实现,可以确保集合元素处于排序状态。TreeSet支持两种排序方式:自然排序和定制排序,默认情况下采用自然排序。
下面看一例子:

   public static void main(String[] args) {
   	TreeSet ts = new TreeSet();
   	ts.add("dsa");
   	ts.add("abc");
   	ts.add("a");
   	Iterator it = ts.iterator();
   	while(it.hasNext()){
   		System.out.println(it.next());
   	}
   }

输出结果:

a
abc
dsa

变换上面add方法的添加顺序,你会发现,输出结果始终不变。

分析:Java提供了一个Compare接口,该接口定义了一个compareTo(Object obj) 方法,该方法返回一个整数值。实现该接口的类就必须实现该方法,实现了该接口的对象就可以比较大小。换句话说,无论你是自定义对象还是系统定义对象,你想放进TreeSet集合,你就必须实现Compareable接口中的compareTo(Object obj) 方法。上述集合添加的元素为String对象。String就实现了Compareable接口。总归来说: TreeSet要排序,就要让元素自身具备比较性。元素需要实现Comparable接口,覆盖CompareTo方法。
Java Set接口_第1张图片
Java Set接口_第2张图片
大部分类在实现compareTo(Object obj) 方法,都需要将比较对象obj强制转化成相同类型,因为只有相同类的两个实例才能比较大小。当试图把一个对象添加到TreeSet中,TreeSet会调用该对象的compareTo(Object obj) 方法与集合中其他元素进行比较。如果不是同一类元素,会发生ClassCastException(类型转换)异常。当一个对象调用另一个对象进行比较时,例如obj1.compareTo(obj2),如果该方法放回0,则表明两个对象相等,如果该方法返回一个正整数,表明obj1大于obj2,如果返回一个负整数,表明obj1小于obj2.

2、TreeSet存储自定义对象
需求: 往TreeSet集合中存自定义对象学生,想按照学生的年龄进行排序。
分析: 年龄一样,姓名不同的情况要考虑!!!

	public static void main(String[] args) {
		
		TreeSet ts = new TreeSet();
		ts.add(new Student("a1",11));
		ts.add(new Student("a2",12));
		ts.add(new Student("a3",13));
		ts.add(new Student("a2",13));
		Iterator it = ts.iterator();
		while(it.hasNext()){
			System.out.println(it.next());
		}
}

// TrteeSet中的元素要具备比较性,需要实现Comparable接口,实现compareTo方法
class Student implements  Comparable
{
	private String name;
	private int age;
	public Student(String n,int a){
		this.name = n;
		this.age = a;
	}
	public int compareTo(Student obj){
	/*
		if(this.age > obj.age){
			return 1;
		//如果年龄一样,不继续判断姓名。同年龄不同姓名的元素就存不进去了。
		}else(this.age == obj.age){
			return this.name.compareTo(obj.name);
		}
		return -1;
	*/
	// 下面是优化后的代码
	int num = new Integer(this.age).compareTo(new Integer(obj.age));
		if(num == 0){
		   return this.name.compareTo(obj.name);
		}
		return num;
	}
	public String toString(){
		return this.name+"::"+this.age;
	}
}

注意: 排序时,当主要条件相同时,一定要判断次要条件

3、数据结构:二叉树

既然TreeSet是有序的,那么如何确定元素的位置呢?

		public static void main(String[] args) {
		TreeSet ts = new TreeSet();
		ts.add(new Student("lisi02",22));
		ts.add(new Student("lisi007",20));
		ts.add(new Student("lisi09",19));
		ts.add(new Student("lisi08",19));
		ts.add(new Student("lisi11",40));
		ts.add(new Student("lisi16",30));
		ts.add(new Student("lisi10",29));
		ts.add(new Student("lisi22",90));
		ts.add(new Student("lisi007",20));

      	Iterator it = ts.iterator();
		while(it.hasNext()){
			System.out.println(it.next());
		}
		}

输出结果如下:

li09::19
lisi08::19
li007::20
lisi007::20
lisi02::22
lisi10::29
lisi16::30
lisi11::40
lisi22::90

转化成图标结构如下(原则:小的放右边,大的放左边):
Java Set接口_第3张图片

分析:从中我们可以看出,当对象ts.add(new Student("lisi11",40));被添加时候,现与对象new Student("lisi12",20)对比,发现年龄比22大。左边的都不用对比了。节省了比较次数,提高了性能。二叉树到元素一多以后,会自动去折中值先进行比较,从中间向两边散发

注意 :在TreeSet集合比较对象,它只看Compare结果。 如果,你始终将CompareTo返回值为1,意思就是怎么存进去,怎么取出来;返回-1,就是逆序;返回0,只存进一个元素。

4、比较器 compator
如果一个类不能用于实现 java.lang.Comparable或者不喜欢默认的comparable行为(改写系统对象),提供自己的排序顺序,我们可以实现Comparator接口来定义一个比较器。
TreeSet的构造方法有如下:Java Set接口_第4张图片

我举一个例子: 字符串根据长度排序。字符串自己实现了Comparable接口,但是并不满足我们的需求。因此,我们需要自定义比较器。

//自定义比较器
class StrLengthCompare implements Comparator
{	
	public int compare(String obj1,String obj2){
		int num = new Integer(obj1.length()).compareTo(new Integer(obj2.length()));
		if(num == 0){
			return obj1.compareTo(obj2);
		}
		return num;
	}
}
//main方法如下
	public static void main(String[] args) {
		
		TreeSet ts = new TreeSet(new StrLengthCompare());
        ts.add("abcd");
        ts.add("cc");
        ts.add("cba");
        ts.add("z");
        ts.add("helloofa");
        
        Iterator it = ts.iterator();
        while(it.hasNext()){
        	System.out.println(it.next());
        }
       }

5、保证元素唯一性
保证元素唯一性的一句:conpareTo方法return0。

注意:
无论是comparable接口的compareTo方法,还是自定义类去实现compator接口的compare方法,当返回值为0的时候,仅仅是表示两个对象在同一位置。对于TreeSet而言,判断两个对象不相等的标准是两个对象通过equals方法比较返回false或者通过compareTo(object obj)比较没有返回为0.即使两个对象为同一对象,TreeSet也会把他当成两个对象处理。

你可能感兴趣的:(Java,成长之路)