JAVA基础(集合进价) —— Set集合

通过之前对 JAVA基础 —— 集合 的学习,我们对于集合的概念以及ArrayList都有了初步的了解。

Collection单列集合JAVA基础(集合进阶) —— Collection单列集合
 List集合JAVA基础(集合进阶) —— List集合
泛型JAVA基础(集合进阶) —— 泛型
Set集合JAVA基础(集合进价) —— Set集合

接下来我们将整体的了解集合的体系结构。


目录

 一、HashSet

1. 哈希值

  对象的哈希值特点:

2. HashSet底层原理

 3. 练习: 利用HashSet集合去除重复元素

二、 LinkedHashSet

三、 TreeSet

1.  TreeSet集合默认的规则

 2. TreeSet的两种比较方式

四、 使用场景


Set系列集合:

  • 无序:存取顺序不一样
  • 不可重复:可以去除重复
  • 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。

 Set集合的实现类:

  • HashSet:无序、不重复、无索引
  • LinkedHashSet:有序、不重复、无索引
  • TreeSet:可排序、不重复、无索引

Set接口中的方法上基本上与Collection的API一致 ,所以在Set集合中没有额外方法,详细见Collection常用方法

public class SetTest {
	public static void main(String[] args) {
		// 1.创建Set集合对象
		Set<String> s = new HashSet<>();

		// 2.添加元素
		// 如果当前元素是第一次添加,那么可以添加成功,返回true
		// 如果当前元素是第二次添加,那么添加失败,返回false
		s.add("张三");
		s.add("李四");
		s.add("王五");

		// 3.打印集合
		// 无序
		System.out.println(s); // [李四, 张三, 王五]

		// 迭代器遍历
		Iterator<String> it = s.iterator();
		while (it.hasNext()) {
			String str = it.next();
			System.out.println(str);
		}

		// 增强for
		for (String str : s) {
			System.out.println(str);
		}

		// lambda表达式
		s.forEach(str -> System.out.println(str));
	}
}

 一、HashSet

HashSet底层原理:

  • 无序、不重复、无索引
  • HashSet集合底层采取哈希表存储数据。
  • 哈希表是一种对于增删改查数据性能都较好的结构。

哈希表组成:

  • JDK8之前:数组+链表
  • JDK8开始:数组+链表+红黑树

1. 哈希值

  • 概括hashCode方法算出来的int类型的整数
  • 该方法定义在Object类中,所有的对象都可以调出,默认使用地址值进行计算
  • 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值

  对象的哈希值特点:

  • 如果没有重写hashCode方法,不用对象计算出的哈希值是不同的。

  •  如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的。

  •  在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞
public class HashSetTest {
	public static void main(String[] args) {
		// 创建对象
		Student s1 = new Student("zhangsan", 23);
		Student s2 = new Student("lisi", 24);
		Student s3 = new Student("wangwu", 25);

		// 1.如果没有重写hashCode方法,不用对象计算出的哈希值是不同的。
		System.out.println(s1.hashCode()); // 2018699554
		System.out.println(s2.hashCode()); // 1311053135

		// 2.如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的。
		System.out.println(s1.hashCode()); // -1461067292
		System.out.println(s2.hashCode()); // -1461067292

		// 3.  在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。
		System.out.println("abc".hashCode()); // 96354
		System.out.println("bcD".hashCode()); // 96354
	}
}



public class Student {
	private String name;
	private int age;

	public Student() {
	}

	public Student(String name, int age) {
		this.name = name;
		this.age = 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;
	}
	
	@Override
	public int hashCode() {
		return Objects.hash(name,age);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null || getClass() != obj.getClass())
			return false;
		Student student = (Student)obj;
		return age == student.age && Objects.equals(name, student.name); 
	}

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
}

2. HashSet底层原理

  1.  创建一个默认长度16,默认加载因子为0.75的数组,数组名为table 。

  2. 根据元素的哈希值跟数组的长度计算出应存入的位置

  3. 判断当前位置是否为null,如果是null直接存入。
  4. 如果位置不为null,表示有元素,则调用equals方法比较属性值。
  5. 一样不存
  • JDK8以后,当链表长度超过8,而且数组长度大于等于64时,会自动转换成红黑树。
  • 如果集合中存储的是自定义对象,必须重写hashCodeequals方法。

 3. 练习: 利用HashSet集合去除重复元素

需求:创建一个存储学生对象的集合,存储多个学生对象。
使用程序实现在控制台遍历该集合。
要求:学生对象的成员变量值相同,我们就认为是同一个对象。

public class HashSetTest {
	public static void main(String[] args) {
		// 创建三个对象
		Student s1 = new Student("zhangsan", 23);
		Student s2 = new Student("lisi", 24);
		Student s3 = new Student("wangwu", 25);
		Student s4 = new Student("zhangsan", 23);

		//2.创建集合用来添加学生
		HashSet<Student> hs = new HashSet<>();
		
		//3.添加元素
		//重写hashCode和equals方法
		System.out.println(hs.add(s1)); //true
		System.out.println(hs.add(s2)); //true
		System.out.println(hs.add(s3)); //true
		System.out.println(hs.add(s4)); //false
		
		//4.打印集合
		System.out.println(hs);
		//[Student [name=wangwu, age=25], Student [name=lisi, age=24],
        // Student [name=zhangsan, age=23]]
	}
}

二、 LinkedHashSet

LinkedHashSet底层原理:

  • 有序、不重复、无索引
  • 这里的有序指的是保证存储取出的元素顺序一致
  • 原理:底层数据结构依然是哈希表,只是每一个元素又额外的多了一个双链表的机制去记录存储的顺序。

在以后如果要数据去重,我们使用哪一个?

默认使用HashSet,如果要求去重且存在有序,才使用LinkedHashSet。

public class LinkedHashSetTest {
	public static void main(String[] args) {
		// 创建三个对象
		Student s1 = new Student("zhangsan", 23);
		Student s2 = new Student("lisi", 24);
		Student s3 = new Student("wangwu", 25);
		Student s4 = new Student("zhangsan", 23);

		// 2.创建集合用来添加学生
		LinkedHashSet<Student> lhs = new LinkedHashSet<>();

		// 3.添加元素
		// 重写hashCode和equals方法
		System.out.println(lhs.add(s1)); // true
		System.out.println(lhs.add(s2)); // true
		System.out.println(lhs.add(s3)); // true
		System.out.println(lhs.add(s4)); // false

		// 4.打印集合
		System.out.println(lhs);
	}
}

三、 TreeSet

TreeSet的特点:

  • 不重复、无索引、可排序
  • 可排序:按照元素的默认规则(由小到大)排序。
  • TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都比较好。
public class TreeSetTest {
	public static void main(String[] args) {
		// 创建TreeSet集合对象
		TreeSet<Integer> ts = new TreeSet<>();

		// 添加元素
		ts.add(4);
		ts.add(5);
		ts.add(1);
		ts.add(3);
		ts.add(2);

		// 打印集合
		System.out.println(ts); // [1, 2, 3, 4, 5]

		// 遍历集合(三种遍历)
		// 迭代器遍历
		Iterator<Integer> it = ts.iterator();
		while (it.hasNext()) {
			Integer i = it.next();
			System.out.println(i);
		}

		// 增强for遍历
		for (Integer it1 : ts) {
			System.out.println(it1);
		}

		// lambda表达式遍历
		ts.forEach(it2 -> System.out.println(it2));
	}
}

1.  TreeSet集合默认的规则

  • 对于数组类型:Integer、Double,默认按照从小到大的顺序进行排序。
  • 对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序。

练习: TreeSet对象排序 

需求:创建TreeSet集合,并添加3个学生对象。
学生对象属性:姓名,年龄。
要求按照学生的年龄进行排序。
同年龄按照姓名字母排列(暂不考虑中文)
同姓名,同年龄认为是同一个人。

public class TreeSetTest {
	public static void main(String[] args) {
		//方案一: 默认排序/自然排序
		//Student实现Comparable接口:重写里面方法,指定比较规则
		
		
		// 创建三个学生对象
		Student s1 = new Student("zhangsan", 23);
		Student s2 = new Student("lisi", 24);
		Student s3 = new Student("wangwu", 25);
		
		//创建集合对象
		TreeSet<Student> ts = new TreeSet<>();
		ts.add(s1);
		ts.add(s2);
		ts.add(s3);
		
		System.out.println(ts);
		
		//hashCode equals 和哈希表有关
		//但是TreeSet底层是红黑树
	}
}


//Student.java
public class Student implements Comparable<Student>{
	private String name;
	private int age;

	public Student() {
	}

	public Student(String name, int age) {
		this.name = name;
		this.age = 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;
	}
	

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}

	@Override
	public int compareTo(Student o) {
		//指定排序规则
		//只看年龄 升序排列
		int result = this.getAge() - o.getAge();
		return result;
	}
}

 2. TreeSet的两种比较方式

  1. 方案一: 默认排序/自然排序: JavaBean类实现Comparable接口指定比较规则
  2. 方案二: 比较器排序: 创建TreeSet对象的时候,传递比较器Comparator制定规则。

使用原则:默认情况下使用第一种,如果第一种不能满足当前需求,就是用第二种。

 

 练习:TreeSet对象排序

需求:请自行选择比较器排序和自然排序两种方式;
要求:存入四个字符串,“c" , "ab" , “df” , "qwer"
按照长度排序,如果一样长则按照首字母排序

public class TreeSetTest {
	public static void main(String[] args) {
		//第二种排序方式:比较器排序
		//创建TreeSet对象的时候,传递比较器Comparator制定规则
		
		//创建集合
		TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
			//o1:表示当前要添加元素
			//o2:表示已经在红黑树中存在的元素
			@Override
			public int compare(String o1, String o2) {
				//按照长度排序
				int i = o1.length() - o2.length();
				//如果一样长则按照首字母排序
				i = i == 0 ? o1.compareTo(o2) : i ;
				return i;
			}
		});
		
		//添加数据
		ts.add("c");
		ts.add("ab");
		ts.add("qwer");
		ts.add("df");
		
		//打印集合
		//重写前
		System.out.println(ts); //[ab,c,df,qwr]
		//重写后
		System.out.println(ts); //[c, ab, df, qwer]
	}
}

四、 使用场景

1.如果想要集合中的元素可重复

  • 用ArrayList集合,基于数组的。(用的最多)

2.如果想要集合中的元素可重复,而且当前的增删操作明显多于查询

  • 用LinkedList集合,基于链表的。

3.如果想对集合中的元素去重

  • 用HashSet集合,基于哈希表的。(用的最多)

4.如果想对集合中的元素去重,而且保证存取顺序

  • 用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。

5.如果想对集合中的元素进行排序

  • 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hgngy.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值