Set接口

Set接口:
是Collection的子接口,而且没有额外定义新的方法,使用的都是Collection中声明过的方法
存储数据的特点: 无序性,不可重复性

实现类:HashSet,LinkedHashSet,TreeSet
HashSet:作为Set接口的主要实现类,线程不安全,可以存储null值,即调用add方法时可以往里面add null(有的结构是不能这么干的)
LinkedHashSet:是HashSet的子类,在HashSet的基础之上加了前后指针(或者说链表),体现的效果是让它看似有序,并不是真正的有序,我们如果去遍历LinkedListHashSet当中的元素,是按照添加的顺序来的,就是因为加了前后指针,遍历内部数据时,就按照添加的顺序遍历
TreeSet:底层是用二叉树存的,准确的说是用红黑树存的,特点是:向TreeSet中添加元素的要求是不能像原来那样什么类型的元素都能放,放入TreeSet中的数据要为同一个类new的对象,TreeSet可以按照添加元素的指定属性进行排序,会应用到对象排序接口:Comparable和Comparator

无序性和不可重复性理解(以HashSet为例说明):
无序性: 不等于随机性,HashSet遍历的结果和添加元素的顺序不一致,但每次输出的顺序都是一样的,遍历的时候也有顺序,每次都是这样的一个顺序,如果把HashSet改成LinkedHashSet,则添加的顺序就是遍历的顺序,不是说添加顺序和遍历顺序一样就叫做有序性。无序是相较List而言的。无序性,以HashSet为例,他的底层是用数组存的,JDK7和8有区别,JDK7当中一开始底层创建的数组长度是16,假设我们添加的第一个元素是456,但456可能不在数组的第一个位置,是在数组当中别的位置,比如放在第7个位置,添加的第二个元素也可能不会放在数组的第二个位置,所以从这个角度来说是无序的。存储的数据在底层数组中并非按照索引顺序进行添加,需要根据添加数据的哈希值经过算法处理来决定在存放在数组中的哪个位置

不可重复性:保证添加的元素按照equals进行判断时不能返回true,相同的元素不能添加进来,只能添加一个
往Set中添加的数据是不可以重复的,遍历的时候只有一个那个元素,比如下面的程序的输出结果中只有一个123,如果我们定义了一个类User,并且没有重写equals方法,下面的程序遍历时都会输出这两个User,如果重写equals方法之后还是会输出这两个,并且没有调用重写的equals方法(可以在重写的equals方法中加输出语句,console中并没有打印相关的信息说明没有调用),不可重复性仍然是用equals做的比较,还会涉及到hashCode(),如果我们在重写equals方法的时候没有注释掉hashCode(),那么遍历输出的时候只会输出一个,且还执行过重写的equals方法

public class Test{
	public static void main(String[] args) {
		
		Set set=new HashSet();
		set.add(456);
		set.add(123);
		set.add(123);
		set.add("AA");
		set.add("CC");
		set.add(new User("Tom", 12));
		set.add(new User("Tom", 12));
		set.add(129);
		
		Iterator iterator=set.iterator();
		while(iterator.hasNext()) {
			System.out.println(iterator.next());
		}
	}
}
class User{
	private String name;
	private int age;
	public User(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}	
}

HashSet中元素的添加过程
HashSet的底层是数组,JDK7当中是new的时候数组就创建了,长度为16,添加数据的过程中会进行扩容,JDK8当中是add的时候数组才创建,添加第一个元素的时候,不存在和谁重复的问题,会计算这个元素的哈希值,就是调用这个元素所属类的hashcode方法,重写的hashcode方法要保证哈希值是用对象的属性计算的,哈希值决定了元素在数组中的存放位置(哈希值经过某种算法处理之后得到在底层数组中的存放位置,即为索引位置),再add又要算添加元素的哈希值,决定了在数组中的存放位置,如果这个位置上面没有元素,那就直接放上去,如果要放的位置已经有元素了,他们的哈希值不一定是一样的,假设是哈希值%16得到索引值,同一个索引值可以对应不同的哈希值,如果哈希值不一样,就认为是不一样的元素,新来的元素可以添加成功,此时就是以链表的方式存这两个元素,原来的元素在数组上用指针指向数组外的新的元素形成链表是JDK8的写法,JDK7是新的那个元素放到数组上,指向原来的数组上的那个元素(现在放在数组外面)形成链表
,添加就成功了,如果哈希值一样,不能简单的说这两个元素一样,还要使用equals方法,调用新的元素所在类的equals方法,把数组上的元素作为参数放进去,如果返回值为true,说明是一样的,那么添加失败,如果返回值是false,只是说恰巧哈希值一样,添加成功,如果根据哈希值计算得到的索引值上面没有元素,那就直接添加成功了,如果有元素,注意元素可能不止一个,因为可以以链表的形式存在多个元素,要把这些元素全部进行比较(先从数组上的那个开始比),如果都不一样,就添加成功,新添加的元素与已经存在指定索引位置上的数据以链表的方式进行存储,JDK7中是新添加的元素放到数组中指向原来的元素,即最新的放在最上面,JDK8是最新的放在最下面,如果在比的过程中和某一个equals(调用的是新添加元素的equals方法,这些东西作为参数)为true,那就不会继续比了,添加数据失败

总结:HashSet底层是数组+链表的结构

关于hashCode和equals的重写
一开始我们没有去重写User里面的hashCode,就会调用Object类中的hashCode,可以理解为结果是随机计算的,所以new的两个Use虽然属性相同,但哈希值肯定不一样,添加的时候就会添加到数组中不一样的位置,所以就添加成功了,比都不要比,所以也没有调equals方法,String重写过hashCode,如果字符串的值相同,则能够保证算出来的哈希值是一样
要求:
向Set当中添加的数据,其所在的类一定要重写hashCode方法,和equals方法
重写的hashCode和equals方法尽可能保持一致性
一致性: 即相等的对象(用equals判断是相等的)必须具有相同的散列码(散列码就是哈希值),即两个对象如果equals为true,即属性从内容上比是一样的,这个时候算出来的哈希值也要一样,如果算出来的哈希值是一样的,要保证他们的属性也是一样的,如果属性不一样(相当于equals是false),则尽量算出来的哈希值也不要一样

重写hashCode方法的基本原则:
1)在程序运行时,同一个对象多次调用hashCode()方法,应该返回相同的值
2)当两个对象的equals方法比较返回true时,这两个对象的hashCode方法的返回值也应相等
3)对象中用作equals方法比较的属性,都应该用来计算hashCode值,这也是保证一致性的小技巧
点击source帮我们生成的hashCode基本上能够保证一致性

class User{
	private String name;
	private int age;
	public User(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	@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;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) 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;
	}
	
	
	
}

HashSet和HashMap之间存在联系
new了一个HashSet实际上里面new了一个HashMap,向HashSet中添加数据,实际上是把数据添加到HashMap中去了

LinkedHashSet
不能这么说:如果使用HashSet,则添加的顺序和遍历输出的顺序不一样,所以是无序的,没有因果关系, LinkedHashSet遍历输出的顺序和添加顺序一样,但LinkedHashSet还是无序的,因为是HashSet的子类,所以存储结构没有变,最开始的时候也是数组,添加的时候存放的操作也是一样的,但因为它有一个链表,如果添加元素成功,还会添加两个地址,表示前面放的那个元素和后面放的元素的位置,放第一个元素的时候,代表前一个存放的元素的那个地址就是null,如果成功添加第二个元素,因为next是用来表明下一个成功存放的元素的位置,所以第一个元素的next就指向第二个存放的元素,第二个元素也指向第一个,相当于LinkedHashSet在原有的HashSet的基础上又额外的提供了一个双向链表,来记录添加元素的先后顺序,即在添加数据的同时每个数据还维护了两个引用。
优点: 对于频繁的遍历操作,LinkedHashSet的效率高于HashSet,找到第一个就能够定位第二个在哪,接着就能够找到第三个在哪,不用像HashSet那样要一个一个在数组中去找,而且还要找链表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值