JAVA基础——集合框架(三)Set集合,HashSet ,LinkedSet,TreeSet集合的介绍及原理

一、 Set集合概述及特点

因为Set集合的API方法和Collection集合一模一样。故没有没有它的特殊的方法。
所以我们只要学它的两个子类,一个HashSet和另外一个TreeSet

二、 HashSet存储字符串并遍历

实现Set接口,由哈希表(实际是一个hashmap对象)支持,它不保证set的迭代顺序;特别是它不保证该顺序恒久不变。此类允许下使用null元素。
案例:

HashSet<String> hashSet =new HashSet<String>();
		 boolean b1 = hashSet.add("a");
		 boolean b2= hashSet.add("a");
		 boolean b3= hashSet.add("b");
		 System.out.println(b1);			
		 System.out.println(b2);
		 System.out.println(b3);
		 System.out.println(hashSet);				
//HashSet的继承体系中有重写toString方法
		 for (String string : hashSet) {
    
    
			System.out.println(string);				
//只要使用那个迭代器迭代,那么就可以使用foreach循环
}	

效果如下:
在这里插入图片描述
tips:Set集合无索引,不可以重复,无序(存储不一致)它的继承系统中有toString方法,故不需要书写toString()方法。

三、 HashSet存储自定义对象

存储自定义对象,并保证元素唯一性。
例:
注意因为set集合不保证元素的有序性,所以每次存储都是无序的

HashSet<Person> hashSet =new HashSet<Person>();
		hashSet.add(new Person("张三",12));
		hashSet.add(new Person("张三",12));
		hashSet.add(new Person("李四",13));
		hashSet.add(new Person("李四",13));
		hashSet.add(new Person("李四",13));
		System.out.println(hashSet);

效果如下:
在这里插入图片描述当我们重写equals方法,使我们出现的姓名和年龄相同就表示存储人物相同。但是在Set集合中我们并不能用equals方法进行判断。而是先要设置hasCode的值。

@Override
	public boolean equals(Object obj) {
    
    
		Person person =(Person)obj;
		return this.name.equals(person.name) &&this.age ==person.age;
	}
	@Override
	public int hashCode() {
    
    
		// TODO Auto-generated method stub
		
		return 10;
	}

当hasCode返回值相同时,说明我们内存中的存储的某类对象会处在相同位置。然后才会调用equals方法,使用equas方法进行判断如果我们的姓名和年龄相同则返回true,不进行对象存储。如果返回false,则会采用筒状式,将对象存储起来。如图所示:
但是为了保证程序的效率,我们尽可能的保证hasCode不相同,然后再执行equals方法。当然如果属性值相同的化,那么它的hasCode的值必然是相同的。属性不相同的返回值尽可能不同。Eclipse等编译器可以自动帮我们生成代码块。快捷键:ctrl+shifts +h 即可生成。并且我们勾选判断的属性。画图如下:
在这里插入图片描述

@Override
	public boolean equals(Object obj) {
    
    
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age != other.age)
			return false;
		if (name == null) {
    
    
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	@Override
	public int hashCode() {
    
    
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

注意该处为什么prime是31?
好处:31是一个质数,质数是能被1和自己本身整除的数。并且31不大也不小。

四、 HashSet如何保证元素唯一性的原理

1.HashSet原理(重点)

  • 我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数
  • 当HashSet调用add()方法存储对象的时候, 先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象
  • 如果没有哈希值相同的对象就直接存入集合
  • 如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存

2.将自定义类的对象存入HashSet去重复

  • 类中必须重写hashCode()和equals()方法
  • hashCode(): 属性相同的对象返回值必须相同, 属性不同的返回值尽量不同(提高效率)
  • equals(): 属性相同返回true, 属性不同返回false,返回false的时候存储。

五、 LinkedHashSet的概述和使用

LinkedHashSet概述:

  • 它的底层是链表实现的,是set集合中唯一一个能保证怎么存就怎么取的集合。(因为它是链表实现,可以记录前后继的地址值。)
  • 它是HashSet的子类,所以可以保证元素是唯一的,与HashSet原理一样。
    LinkedHashSet的特点:可以保证怎么存就怎么取。意思就是你是怎么放进去的就可以怎么取出来
LinkedHashSet<String> linkedHashSet =new LinkedHashSet<String>();
		linkedHashSet.add("a");
		linkedHashSet.add("a");
		linkedHashSet.add("a");
		linkedHashSet.add("b");
		linkedHashSet.add("b");
		linkedHashSet.add("c");
		linkedHashSet.add("d");
		
		System.out.println(linkedHashSet);

效果如下:
在这里插入图片描述

六、 TreeSet存储Integer类型的元素并遍历

TreeSet是用来对元素进行排序的,并且它也是不存储相同数据的。

TreeSet<Integer> treeSet =new TreeSet<Integer>();
	treeSet.add(1);
	treeSet.add(1);
	treeSet.add(3);
	treeSet.add(3);
	treeSet.add(2);
	treeSet.add(2);
	treeSet.add(4);
	System.out.println(treeSet);

效果如下:
在这里插入图片描述

七、 TreeSet存储自定义对象

当我们单纯的去存储对象的时候,会进行报错。因为对象不能用来做比较。所以我们需要给我们bean类添加接口方法。

public class Person implements Comparable<Person>{
    
    
	private String name;
	private int age;
	public String getName() {
    
    
		return name;
	}
	public void setName(String name) {
    
    
		this.name = name;
	}
	public int getAge() {
    
    
		return age;
	}
	public void setAge(int age) {
    
    
		this.age = age;
	}
	
	public Person(String name, int age) {
    
    
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
    
    
		return "Person [name=" + name + ", age=" + age + "]";
	}
	@Override
	public int compareTo(Person o) {
    
    
		// TODO Auto-generated method stub
		
		return 1;
	}
}

TreeSet部分

TreeSet<Person> treeSet =new TreeSet<Person>();
	treeSet.add(new Person("张三",12));
	treeSet.add(new Person("李四",12));
	treeSet.add(new Person("王五",13));
	treeSet.add(new Person("赵六",14));
	System.out.println(treeSet);

效果如下:
在这里插入图片描述
注意:CompareTo方法的返回值:
如果return返回的数是正数,集合就是怎么存怎么取
如果return返回的数是负数,集合就是倒序存储
如果return返回的数是0,集合中就存在一元素

八、 TreeSet保证元素唯一和自然排序的原理

在这里插入图片描述
当我们按照年龄进行排序

书写Bean包中的Person实体类

@Override
	public int compareTo(Person o) {
    
    
		// TODO Auto-generated method stub
		return this.age -o.age;
	}
TreeSet<Person> treeSet =new TreeSet<Person>();
	treeSet.add(new Person("张三",12));
	treeSet.add(new Person("李四",22));
	treeSet.add(new Person("周七",24));
	treeSet.add(new Person("王五",10));
	treeSet.add(new Person("赵六",24));
	System.out.println(treeSet);

原理如下:
在这里插入图片描述
效果如下:
在这里插入图片描述
此时我们会发现,少一个赵六的元素,很简单,因为我们只进行了年龄判断,所以后面存储的年龄相同的元素不会被存储进来。
如果我们进行升级:按照年龄进行排序

@Override
	public int compareTo(Person o) {
    
    
		// TODO Auto-generated method stub
		int num =this.age -o.age;
		return num == 0? this.name.compareTo(o.name):num;
	}

该方法会进行完整的排序。

九、TreeSet存储自定义对象并遍历练习1(按照姓名排序)

@Override
	public int compareTo(Person o) {
    
    
		int num = this.name.compareTo(o.name); 		
		//按照姓名排序
		return num== 0? this.age -o.age :num;		
		//年龄是次要条件
	}
TreeSet<Person> treeSet =new TreeSet<Person>();
treeSet.add(new Person("李四",22));
treeSet.add(new Person("张三",12));
treeSet.add(new Person("周七",24));
treeSet.add(new Person("王五",10));
treeSet.add(new Person("赵六",24));
System.out.println(treeSet);

效果如下:
在这里插入图片描述

十、 TreeSet存储自定义对象并遍历练习2(按照姓名的长度排序)

	@Override
	public int compareTo(Person o) {
    
    
		// TODO Auto-generated method stub
		int length =this.name.length() - o.name.length();	
		//比较姓名长度
		int num =length == 0 ?this.name.compareTo(o.name):length;
		//完全有一种可能,名字长度一样,内容是不一样的
		return num==0? this.age -o.age:num;		
		//完全有一种可能,姓名和长度均相同
	}

十一、 TreeSet保证元素唯一和比较器排序的原理

TreeSet(Collection <? super E> comparator):构造一个新的空TreeSet,他根据指定比较器进行排序
需求:将字符串按照长度排序

TreeSet<String> treeSet =new TreeSet<String>(new CompareByLen());			
//Comparator c = new CompareByLen();
	treeSet.add("aaaaaaaaaa");
	treeSet.add("z");
	treeSet.add("wc");
	treeSet.add("nba");
	treeSet.add("cba");
	System.out.println(treeSet);

新建一个类

class CompareByLen  implements Comparator<String>{
    
    
	@Override
	public int compare(String s1, String s2) {
    
    		
//按照字符串的长度比较
		// TODO Auto-generated method stub			
		int num = s1.length() -s2.length();			
//长度为主要条件
		return num==0? s1.compareTo(s2): num ;		
//内容为次要条件
	}
	
}

效果如下:
在这里插入图片描述原理如下:
在这里插入图片描述

依次从左边就先取出,如果没有的话就取出其右边。

十二、 TreeSet原理

1.特点
TreeSet是用来排序的, 可以指定一个顺序, 对象存入之后会按照指定的顺序排列
2.使用方式

a.自然顺序(Comparable)

  • TreeSet类的add()方法中会把存入的对象提升为Comparable类型
  • 调用对象的compareTo()方法和集合中的对象比较
  • 根据compareTo()方法返回的结果进行存储

b.比较器顺序(Comparator)

  • 创建TreeSet的时候可以制定 一个Comparator
  • 如果传入了Comparator的子类对象, 那么TreeSet就会按照比较器中的顺序排序
  • add()方法内部会自动调用Comparator接口中compare()方法排序
  • 调用的对象是compare方法的第一个参数,集合中的对象是compare方法的第二个参数

c.两种方式的区别

  • TreeSet构造函数什么都不传, 默认按照类中Comparable的顺序(没有就报错ClassCastException)
  • TreeSet如果传入Comparator, 就优先按照Comparator

猜你喜欢

转载自blog.csdn.net/Mr_GYF/article/details/108719802