1、Set 接口介绍
- java.util.Set 接口extends Collection 接口
- 特点:
- 无序, 不重复的元素。
- 没有索引,也不能用带索引的方法, 不能使用普通的for循环遍历
Set集合取出元素的方式可以采用:迭代器、增强for。
》》》》》》》》》》》》》》》》》》》》》》》》》
Set 接口子类
2、HashSet集合介绍
- java.util.HashSet 集合,implements Set接口,java.util.HashSet是Set接口的一个实现类
- HashSet特点:
- 无序,存储元素和取出元素的顺序有可能不一致
- 不重复的元素。
- 没有索引,也不能用带索引的方法
- 底层是一个Hash表结构,查询速度非常快
java.util.HashSet底层的实现其实是一个java.util.HashMap支持。
HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCode与equals方法。
package listandset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* created by apple on 2020/6/20
* java.util.Set 接口extends Collection 接口
* 特点:
* 无序, 不重复的元素。
* 没有索引,也不能用带索引的方法
*
* java.util.HashSet 集合,implements Set接口
* HashSet特点:
* 无序,存储元素和取出元素的顺序有可能不一致
* 不重复的元素。
* 没有索引,也不能用带索引的方法
* 底层是一个哈希表结构,查询速度非常快
*
*/
public class Demo1Set {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
//往集合中添加元素
set.add(1);
set.add(2);
set.add(4);
set.add(1);
//没有索引,不能用普通for循环,用迭代器遍历
Iterator<Integer> it = set.iterator();
while (it.hasNext()){
System.out.println(it.next()); // 1 2 4
}
//增强for遍历
System.out.println("=====");
for (Integer i : set) {
System.out.println(i); // 1 2 4
}
}
}
2.1、哈希值 十进制的整数
package listandset;
/**
* created by apple on 2020/6/20
*
*/
public class Person extends Object{
//重写hashCode方法 ..那之前的hashCode值是1
@Override
public int hashCode() {
return 1;
}
}
package listandset;
/**
* created by apple on 2020/6/20
* 哈希值: 是一个十进制的整数 ,由系统随机给出(就是对象的地址值,是模拟出来得到的地址,不是数据实际存储的物理地址)
* 在Object类有一个方法,可以获取对象的hash值
* int hashCode()
* 返回对象的hash值
* hashCode 方法的源码:
* public native int hashCode();
* native 代表该方法调用的是本地操作系统的方法
*/
public class DemohashCode {
public static void main(String[] args) {
//Person 类继承了Object类, 所以可以使用Object的hashCode方法
Person person = new Person();
int h1 = person.hashCode();
System.out.println(h1);// 1625635731 |1
Person person2 = new Person();
int h2 = person2.hashCode();
System.out.println(h2);// 1580066828 |1
/*
toString 方法的源码
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*/
System.out.println(person); //listandset.Person@60e53b93 60e53b93 是1625635731的16进制表现形式
System.out.println(person2); //listandset.Person@5e2de80c 5e2de80c 是1625635731的16进制表现形式
System.out.println(person == person2); //false
//String类的hash值,重写了Object类的hashCode方法,
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.hashCode()); //96354
System.out.println(s2.hashCode()); //96354
System.out.println("重地".hashCode()); // 1179395
System.out.println("通话".hashCode()); // 1179395
}
}
1
1
listandset.Person@1
listandset.Person@1
false
96354
96354
1179395
1179395
2.2、HashSet集合存储数据的结构(哈希表)
哈希表红黑树查询速度也快,折半查找
什么是哈希表呢?
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
看到这张图就有童鞋要问了,这个是怎么存储的呢?看下图就明白了
总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
2.3、HashSet集合存储元素不重复的原理
package listandset;
import java.util.HashSet;
/**
* created by apple on 2020/6/20
* Set集合不允许存储重复元素的原理
*/
public class Demo01HashSet {
public static void main(String[] args) {
//创建hashSet
HashSet<String> strings = new HashSet<>();
strings.add("abc");
strings.add("abc");
strings.add("重地");
strings.add("通话");
strings.add("abc");
System.out.println(strings); // [重地, 通话, abc]
}
}
[重地, 通话, abc]
set集合存储元素不重复的原理
哈希表:数组+ 链表
Set集合在调用add方法时,add方法会调用元素的hashCode方法和equals方法,判断元素是否重复。
set.add("abc");
add方法会调用abc的hashcode方法,计算器哈希值:96354,
在集合中没有找到96354这个值,
集合就会把"abc" 存储到集合中。
set.add("abc")
add方法会调用abc的hashcode方法,计算器哈希值:96354,
在集合中有找到96354这个值,哈希冲突。
调用equals方法,和哈希值相同的元素比较,"abc".equals("abc"),返回true,两个元素的hash值相同,equals方法返回true,认定两个元素相同,就不会把第二个abc放入集合中。
而重地和通话的hashcode值相同,但是没有equals,所以以链表的形式链在下面
2.4、HashSet存储自定义类型元素
没有重写hashcode和equals方法, 不能把同一个人视为同一个人
package listandset;
/**
* created by apple on 2020/6/20
*/
public class PersonHashSet {
private String name;
private int age;
public PersonHashSet() {
}
public PersonHashSet(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "PersonHashSet{" +
"name='" + name + '\'' +
", 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;
}
}
package listandset;
import java.util.HashSet;
/**
* created by apple on 2020/6/20
* HashSet存储自定义类型元素
* set集合保证元素唯一,存储的元素可以是任意类型。
* 必须重写hashcode equals方法。
*
* 要求:同名和同年龄的人视为同一个人,只能存储一次
*/
public class HashSetSavePerson {
public static void main(String[] args) {
//创建一个HashSet 集合,存储PersonHashSet
HashSet<PersonHashSet> set = new HashSet<>();
PersonHashSet p1 = new PersonHashSet("小美女", 18);
PersonHashSet p2 = new PersonHashSet("小美女", 18);
PersonHashSet p3 = new PersonHashSet("小女", 18);
System.out.println(p1.hashCode()); //1625635731
System.out.println(p2.hashCode()); //1580066828
System.out.println(p1.equals(p2)); //哈希值不同
System.out.println(p1 == p2); //false,equlas 方法默认比较两个对象的地址值 ,比较地址值
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println(set); //没有重写HashCode方法和equals方法。 [PersonHashSet{name='小美女', age=18}, PersonHashSet{name='小美女', age=18}, PersonHashSet{name='小女', age=18}]
}
}
输出:
1625635731
1580066828
false
false
[PersonHashSet{name='小美女', age=18}, PersonHashSet{name='小美女', age=18}, PersonHashSet{name='小女', age=18}]
重写hashCode和equals 方法,可以比较出来是同一个人,重写的方法是自动生成的哦
package listandset;
import java.util.Objects;
/**
* created by apple on 2020/6/20
*/
public class PersonHashSet {
private String name;
private int age;
public PersonHashSet() {
}
public PersonHashSet(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "PersonHashSet{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PersonHashSet that = (PersonHashSet) o;
return age == that.age &&
Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(name, 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;
}
}
最后输出:
734175839
734175839
true
false
[PersonHashSet{name='小女', age=18}, PersonHashSet{name='小美女', age=18}]
》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
3、LinkedHashSet
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?在HashSet下面有一个子类java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构, 多了一层链表,保证有序
package listandset;
import java.util.HashSet;
import java.util.LinkedHashSet;
/**
* created by apple on 2020/6/20
* java.util.LinkedHashSet 集合extends HashSet 集合
* LinkedHashSet特点:
* 底层是一个哈希表(数组+ 链表/红黑树)+ 链表,多了一条链表,用来记录元素的存储顺序,保证元素有序 HashSet
*/
public class DemoLinkedHashSet {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("www");
set.add("abc");
set.add("abc");
set.add("ifta");
System.out.println(set); //[abc, www, ifta] 无序,不允许重复
LinkedHashSet<String> set1 = new LinkedHashSet<>();
set1.add("www");
set1.add("abc");
set1.add("abc");
set1.add("ifta");
System.out.println(set1); //[www, abc, ifta] 有序的,不允许重复
}
}
[abc, www, ifta]
[www, abc, ifta]
4、可变参数
package listandset;
/**
* created by apple on 2020/6/20
* 可变参数:在jdk1.5之后的出现的新特性
* 使用前提:
* 当方法的参数列表【数据类型】已经确定,但【参数个数】不确定,可以使用可变参数
* 使用格式: 定义方法时使用
* 修饰符 返回值类型 方法名(数据类型... 变量名){}
* 可变参数的原理:
* 底层是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数,传递的参数格式可以是0个(不传递),1,2,。。。多个
* 可变参数的注意事项:
* 1、一个方法的参数列表,只能有一个可变参数
* 2、如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
*/
public class DemoVar {
public static void main(String[] args) {
int i = addint();
System.out.println("没有参数:" + i);
int i2 = addint(10,20,30,40,50);
System.out.println("一个参数:" + i2);
}
//定义一个方法,计算两个int类型整数的和
/*
定义计算(0-n)整数和的方法。
数据类型确定了,int
参数的个数不确定,不知道要计算几个整数的和,就可以使用可变参数
add() 会创建一个长度为0 的数组。 new int[0]
add(10) 会创建一个长度为1 的数组,存储传递过来的参数 new int[]{10}
传递过来几个参数,数组长度就是几
*/
/* public static int addint(int a ,int b) {
return a + b;
}*/
public static int addint(int...arr) {
//System.out.println(arr); //数组的地址[I@60e53b93,底层是一个数组
// System.out.println("数组长度:" + arr.length); //0 ,长度为0 的数组 。1
//定义一个初始化的变量,记录累加求和,
int sum = 0;
//遍历数组,获取数组中的元素
for (int i : arr) {
//累加求和
sum+= i;
}
return sum;
}
}
没有参数:0
一个参数:150
网友评论