I学霸官方免费教程三十二:Java集合框架之Set集合-程序员宅基地

技术标签: java  开发工具  数据结构与算法  

Set接口

Set集合是无序的、元素不可重复的结合
常用集合类有HashSet和TreeSet


HashSet类
常用的两种List集合各有各的优点,那么有没有同时具备这两种List集合的优点的集合呢?答案是肯定的,就是Set集合。

实例:
package collection.set.hashSet;
import java.util.HashSet;
import java.util.Iterator;
/**
 * 演示HashSet
 * @author 学霸联盟 - 赵灿
 */
public class HashSetDemo {
	public static void main(String[] args) {
		//创建HashSet对象
		HashSet hs = new HashSet();
		//循环向集合hs中添加元素
		for (int i = 0; i < 10; i++) {
			//创建Person对象
			Person p = new Person();
			//为Person对象的name属性赋值
			//有此处可知,加入的顺序是赵0、赵1、赵2......赵9
			p.name = "赵" + i;
			//将Person对象p加入集合
			hs.add(p);
		}
		/*
		 * Iterator(迭代器):提供一种访问Collection集合中每个元素的方法
		 * Iterator是一个接口
		 * 其中定义了hasNext()方法,用于判断集合中是否含有下一个元素
		 * 如果还有下一个元素,返回true;反之,返回false
		 * 还定义了next()方法,获取下一个元素
		 * Collection中定义了一个iterator()方法,用于获取Iterator对象
		 * 
		 * 由于Set集合无法使用下标访问元素,无法使用下标的方式遍历集合元素
		 * 所以java为我们提供了Iterator用于访问集合中的各个元素
		 */
		Iterator it = hs.iterator();
		//调用hasNext方法判断集合中是否还有下一个元素
		while(it.hasNext()){
			//调用next方法获取下一个元素
			Object obj = it.next();
			//强制类型转换
			Person p = (Person)obj;
			//输出name属性值,和加入的顺序不同
			System.out.print(p.name + "  ");
		}
	}
}
//Person类
class Person{
	public String name;
}
我的机器上运行的结果:
赵4  赵7  赵8  赵0  赵6  赵1  赵2  赵3  赵5  赵9

      从上面的结果可以看到,输出的顺序并不是加入时的顺序(未必一次就能运行出乱序的效果,可以瞬间连续多次点击eclipse上的运行按钮,这样看到乱序的效果几率比较大);所以称Set是无序的集合。
      那么Set是如何同时实现两种List集合的优点,又为什么没有顺序呢?
      首先Set同时使用了两种List集合的结构,即数组 + 链表(Set中使用的是单项链表:一个节点中只存储下一个节点,而不存储上一个节点);其中数组中的每一个位置均用于存储一个链表第一个元素。
      这里不得不提到hashCode和equals。在创建Set集合的子类对象是,会创建一个Node[] table(节点数组)用于存储Node对象,Node中存储下一个Node和加入集合的元素。
再向集合中添加元素A时,Set会创建一个Node对象nodeA(Node nodeA = new Node()),将元素A赋值给nodeA中key属性(nodeA.key = A),然后根据元素A的hashCode值计算出下标(假如得到的下标是1),最后将nodeA的地址赋值到下标为1的位置中。
      再次向集合中添加元素B时,又会创建Node对象nodeB,使用元素B的hashCode值计算出的下标也是1;此时会用equals方法判断元素A和元素B这两个对象是否相同。
      如果equals返回true,则表示A和B内容相同,此时不会再将nodeB加入集合。
      如果equals方法返回false,说明元素A和元素B是不同的两个对象,此时会将nodeB赋值给nodeA中的next属性(nodeA.next = nodeB),这样就形成了单项链表结构。
      由以上描述可知,Set中不能添加重复的元素,而且无法从Set中快速获取某一个指定的元素,只能使用Iterator为我们提供的遍历集合的方法获取Set中的每一个元素。
示例图


上述中创建的table称作散列表或哈希表,默认初始长度为16(如果创建集合对象时自定义初始容量,自定义的容量值必须是2的整倍数),加载因子默认为0.75(当集合中添加的元素个数等于当前长度乘以加载因子时,集合会自动将容量增加至原来的2倍)。

拓展阅读:《I学霸官方免费教程三十七:Java数据结构之单向链表结构》

在使用key的hashCode值计算下标(计算方法:int h = key.hashCode(); int hash = h^(h>>>16); int index = (table.length - 1) & hash),这样可以保证不越界,但无法保证顺序,所以Set集合是无序的。
这样做带来的优点是当加入和删除元素时,都无需将前后元素在数组中位移,只需根据计算出的下标和equals方法得到的结果,进行加入和删除即可。
缺点是没有顺序,无法快速获取某个元素,必须采用遍历的方式获取Set集合中的元素。

另外,可以利用重写Object类中的hashCode方法,来控制什么样的对象可以生成一致的(节点对象在hash表中的)下标。此时再利用重写Object类的equals方法,来控制对象是否属于重复的对象。而且必须两个方法得到的结果同时满足要求时,才认为两个对象时同一对象;任一方法不满足要求都认为是不同的对象。

实例:
package collection.set.hashSet;
import java.util.HashSet;
import java.util.Set;
/**
 * 演示重写hashCode方法和equals方法
 * 
 * @author 学霸联盟 - 赵灿
 */
public class HashCodeEqualsDemo {
	public static void main(String[] args) {
		/*
		 * 在堆内存中创建了一个PersonHE类型的对象
		 * 对象中保存的是"张三"和"123456789"两个字符串
		 */
		PersonHE p1 = new PersonHE("张三", "123456789", 10);
		/*
		 * 又在堆内存中创建了一个PersonHE类型的对象
		 * 对象中保存的也是"张三"和"123456789"两个字符串
		 */
		PersonHE p2 = new PersonHE("张三", "123456789", 20);
		/*
		 * 此时在内存中有两个PersonHE类型的对象
		 * 如果PersonHE类没有重写父类Object的equals方法
		 * 那么使用的则是Object类中的equals方法比较
		 * 和使用==比较一样,结果是:false
		 */
		System.out.println(p1 == p2);
		/*
		 * 现在已经重写了equals方法,按照姓名和身份证号比较
		 * 创建的时候姓名和身份证号赋的值都一样
		 * 所以此时使用equals比较得到的结果是:true
		 */
		System.out.println(p1.equals(p2));
		//创建HashSet对象(父类引用指向子类对象)
		Set setHE = new HashSet();
		//向集合中添加元素
		setHE.add(p1);
		/*
		 * PersonHE重写了hashCode方法和equals方法
		 * 使得对象p1和p2的hashCode相等,equals的结果为true
		 * 所以不会将对象p2加入集合
		 */
		setHE.add(p2);
		/*
		 * 可以使用Iterator + while循环获取Set集合中的元素
		 * 也可以使用增强for循环
		 * 语法格式:for(数据类型  变量名 : 继承Collection接口的集合或数组){}
		 * 作用:循环一次从集合或数组中取出一个元素赋值变量
		 * 注意:声明变量的数据类型,必须能够接收集合或数组中取出的元素的类型
		 * 反例:String[] str = {"abc", "xyz"};
		 * 		for(int i : str){};
		 * 		执行第一次循环,取出字符串abc,赋值给int类型的变量i
		 * 		这很明显是错误的
		 */
		for(Object obj:setHE){
			PersonHE pHE = (PersonHE)obj;
			/*
			 * 由于加入前一个对象p1被覆盖
			 * 所以这里只会输出一次,而且输出的是对象p1
			 * 输出结果:姓名:张三  身份证号:123456789  年龄:10
			 */
			System.out.println(pHE);
		}
	}
}


/**
 * 创建人类PersonHE
 * @author 学霸联盟 - 赵灿
 */
class PersonHE{
	//姓名
	private String name;
	//身份证号
	private String id;
	//年龄
	public int age;
	//声明带参构造方法
	public PersonHE(String name, String id, int age){
		this.name = name;
		this.id = id;
		this.age = age;
	}
	/**
	 * 重写父类的hashCode
	 * 目的:自定义计算hash值的算法
	 */
	@Override
	public int hashCode() {
		/*
		 * 这里自定义的计算规则是,姓名的hash值亦或身份证号的hash值
		 * 当然算法不是固定的,只要能符合需求即可
		 * 使用亦或运算只是为了尽可能少的出现相同情况
		 */
		return name.hashCode() ^ id.hashCode();
	}
	/**
	 * 重写父类的equals方法
	 * 目的:自定义比较两个对象内容是否相同的规则
	 */
	@Override
	public boolean equals(Object obj) {
		//将Object强制类型转换为Person
		PersonHE p = (PersonHE)obj;
		//这里自定义的比较规则是,当姓名和身份证号相同时,就表示是同一个人
		boolean result = this.name.equals(p.name) && this.id.equals(p.id);
		return result;
	}
	/**
	 * 重写父类的toString方法
	 * 目的:自定义将对象转换成字符串的规则
	 */
	@Override
	public String toString() {
		//这里自定义的规则是 ,返回姓名和身份证号的字符串
		return "姓名:" + name + "  身份证号:" + id + "  年龄:" + age;
	}
}
运行结果:
false
true
姓名:张三  省份证号:123456789  年龄:10

 

总结:

Set集合是无序的,不可重复的集合,采用散列(Hash)存储(数组 + 单向链表),遍历时可以采用Iterator + 循环和增强for循环,Iterator(迭代器)主要方法:hasNext()和next(),判断加入集合中的两个对象是否重复:
1、判断hashCode是否相同;
当hashCode相同,但equals比较结果为false时,两个对象会被保存在一个单向链表上,这个单向链表称为“桶”
2、使用equals判断内容是否相同;
当equals比较结果为true,但hashCode不同时,两个对象会被保存在不同的“桶”中

TreeSet类

既然Set集合是无序的,那么可不可以另外给Set增加排序的方法呢?答案是肯定的。
java为我们提供了TreeSet类,实现了自动对加入Set集合的元素进行排序。但是要求加入到TreeSet集合的元素类型必须实现Comparable接口,并实现接口中的compareTo()方法,在该方法中自定义排序方式。

实例:
package collection.set.treeSet;
import java.util.TreeSet;
/**
 * 演示泛型TreeSet集合
 * @author 学霸联盟 - 赵灿
 */
public class TreeSetDemo {
	public static void main(String[] args) {
		//创建三个树枝对象
		Branch b1 = new Branch("树枝10", 10);
		Branch b2 = new Branch("树枝5", 5);
		Branch b3 = new Branch("树枝8", 8);
		//创建TreeSet集合对象
		TreeSet ts = new TreeSet();
		//向集合中添加元素;加入TreeSet集合的元素类型必须实现Comparable接口
		ts.add(b1);
		ts.add(b2);
		ts.add(b3);
		//增强for循环遍历集合ts
		for (Object obj : ts) {
			//强制类型转换
			Branch b = (Branch) obj;
			//输出
			System.out.println("名称:" + b.getName() + "  年轮" + b.getAnnualRing());
		}
	}
}

/**
 * 树枝类Branch
 * 加入TreeSet集合的元素类型必须实现Comparable接口
 * 否则程序运行的时候会出现异常
 * @author 学霸联盟 - 赵灿
 */
class Branch implements Comparable{
	//标识名称
	private String name;
	//年轮:用于排序的依据
	private int annualRing;
	//带参构造方法
	public Branch(String name, int annualRing){
		this.name = name;
		this.annualRing = annualRing;
	}
	//重写接口中的方法
	@Override
	public int compareTo(Object o) {
		//强制类型转换
		Branch b = (Branch)o;
		/*
		 * 使用年轮作为排序比较的依据
		 * 当前对象年轮减去参数传入的Branch对象的年龄
		 * 结果等于0表示:两个树枝年轮相等,排序不分先后
		 * 结果大于0表示:当前对象的年轮大,排在后面
		 * 结果小于0表示:参数对象的年轮大,排在后面
		 */
		int result = this.annualRing - b.annualRing;
		return result;
	}
	//获取名称
	public String getName(){
		return name;
	}
	//获取年轮
	public int getAnnualRing(){
		return annualRing;
	}
}
运行结果:
名称:树枝5  年轮5
名称:树枝8  年轮8
名称:树枝10  年轮10

 

版权声明:本文为博主原创文章,未经博主允许不得转载。

 

转载于:https://www.cnblogs.com/ixueba/p/4725150.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30384031/article/details/98820606

智能推荐

info级别日志与debug_debug中的计算是否在info级别也会跑-程序员宅基地

文章浏览阅读6.3k次。日志默认info级别debug日志不会打印,但是会执行日志填充的数据例如:logger.debug("日志输出",2*10); 1. 2*10会先执行出结果,然后继续往下走2. 在ch.qos.logback.classic.Logger#filterAndLog_1方法中判断是否符合级别要求是否需要输出3.如图:..._debug中的计算是否在info级别也会跑

Third calibration example - Calibration using Heikkil�'s data (planar and non-planar calibration rig-程序员宅基地

文章浏览阅读1.4k次。Similarly to the previous example, let us apply our calibration engine onto the data that comes with the originalcalibration toolbox of Heikkil� from the University of Oulu. Once again. do not bothe_non-planar calibration

物联网常用的网络协议:MQTT、AMQP、HTTP、CoAP、LwM2M_lmm2m和mqtt-程序员宅基地

文章浏览阅读1w次,点赞10次,收藏63次。物联网常用的网络协议:MQTT、AMQP、HTTP、CoAP、LwM2M物联网设备间沟通的语言,就是网络协议。设备间想相互交流,通信双方必须使用同一种“语言”。比如说你和中国人问好说’你好‘、日本人问好要说‘こんにちは’、和英国人问好要说‘hello’.说起网络协议,你可能马上就想到了 HTTP 协议。是的,在日常的 Web 开发中,我们总是需要跟它打交道,因为 HTTP 协议是互联网的主流网络协议。类似地,应用在互联网中的网络协议,还有收发电子邮件的 POP3 、SMTP 和 IMAP 协议,以及_lmm2m和mqtt

fortran使用MKL函数库中的geev计算一般矩阵的特征值与特征向量_fortran求矩阵特征值-程序员宅基地

文章浏览阅读7.4k次,点赞4次,收藏20次。这篇博文简要记录一下使用MKL函数库计算一般矩阵的特征值与特征向量对形如对称矩阵或是埃尔米特等特殊矩阵有其对应的子程序,在这里先不涉及。有需求的可以自行查阅MKL官方文档下面给出本次示例代码:代码使用f95接口。f77借口参数太多,笔者太懒<不过懒惰是创新的原动力^_^>program testGeev use lapack95 implicit..._fortran求矩阵特征值

Numpy, Scipy, Matplotlib基本用法_np.imresize-程序员宅基地

文章浏览阅读147次。学习内容来自:Numpy Tutorial文章目录Array SlicingArray IndexingMathematical ManipulationBroadcastingImage Processing基本的用法课程里面说的挺详细了。 特别记录一些需要关注的点。Array Slicing使用固定数字进行array寻址会导致数组降维。y = np.random.random((3,..._np.imresize

蓝桥杯 历届试题 回文数字 C++_c++蓝桥杯 回文数-程序员宅基地

文章浏览阅读355次。题目阅览 观察数字:12321,123321 都有一个共同的特征,无论从左到右读还是从右向左读,都是相同的。这样的数字叫做:回文数字。  本题要求你找到一些5位或6位的十进制数字。满足如下要求:  该数字的各个数位之和等于输入的整数。  输入格式  一个正整数 n (10<n<100), 表示要求满足的数位和。  输出格式若干行,每行包含一个满足要求的5位或6位整数。  数字按从小到大的顺序排列。  如果没有满足条件的,输出:-1样例输入144样例输出199899_c++蓝桥杯 回文数

随便推点

Java生成二维码,扫描并跳转到指定的网站_java扫二维码进入自己制作的网页-程序员宅基地

文章浏览阅读6.2k次,点赞3次,收藏13次。需要的pom文件 &lt;dependency&gt; &lt;groupId&gt;com.google.zxing&lt;/groupId&gt; &lt;artifactId&gt;core&lt;/artifactId&gt; &lt;version&gt;3.1.0&lt;/version&gt;_java扫二维码进入自己制作的网页

python:多波段遥感影像分离成单波段影像_一个多波段影像分解成多个单波段影像-程序员宅基地

文章浏览阅读650次。在遥感图像处理中,我们经常需要将多波段遥感影像拆分成多个单波段图像,以便进行各种分析和后续处理。本篇博客将介绍一个用Python编写的程序,该程序可以读取多波段遥感影像,将其拆分为单波段图像,并保存为单独的文件。本程序使用GDAL库来处理遥感影像数据,以及NumPy库来进行数组操作。结果如下图所示,选中的影像为输入的多波段影像,其他影像分别为拆分后的多波段影像。_一个多波段影像分解成多个单波段影像

移动硬盘突然在电脑上无法显示_电脑无法显示移动硬盘-程序员宅基地

文章浏览阅读5.1k次,点赞2次,收藏4次。0前言一直用的好好的移动硬盘突然不显示了,前段时间因为比较忙,一直没顾得上管它,趁这个假期,好好捅咕了一番,总算是弄好了,特此将解决的过程记录如下:1.问题描述 1.我的移动硬盘在其他人的电脑上能够正常显示和使用 2.其他移动硬盘在我电脑上能够正常的显示和使用 3.在我的电脑上,该移动硬盘,既不显示盘符,磁盘管理 又不显示该磁盘2.问题分析1.我的移动硬盘能够在其他人电脑上_电脑无法显示移动硬盘

Linux开机启动过程(16):start_kernel()->rest_init()启动成功_linux 标志着kernel启动完成-程序员宅基地

文章浏览阅读1k次。Kernel initialization. Part 10.在原文的基础上添加了5.10.13部分的源码解读。End of the linux kernel initialization processThis is tenth part of the chapter about linux kernel initialization process and in the previous part we saw the initialization of the RCU and stopped o_linux 标志着kernel启动完成

Scala安装和开发环境配置教程_scala安装及环境配置-程序员宅基地

文章浏览阅读5.3k次,点赞5次,收藏23次。Scala语言概述:Scala语言是一门以Java虚拟机为运行环境,支持面向对象和函数式编程的静态语言,java语言是面向对象的,所以代码写起来就会相对比较模块儿,而函数式编程语言相对比较简洁_scala安装及环境配置

深扒人脸识别60年技术发展史_人脸识别发展历史-程序员宅基地

文章浏览阅读2.4k次。“他来听我的演唱会,门票换了手铐一对”。最近歌神张学友变阿SIR,演唱会上频频抓到罪犯,将人脸识别技术又一次推到了大众的视线中。要说人脸识别技术的爆发,当属去年9月份苹果iPhone x的发布,不再需要指纹,只需要扫描面部就可以轻松解锁手机。任何技术一旦进入智能手机这个消费市场,尤其是被苹果这个标志性的品牌采用,就意味着它将成为一种趋势,一个智能设备的标配。在智能手机快速崛起的这几年,其密码锁..._人脸识别发展历史