文章目录
1.3Set接口
1.set接口概述
Set接口是Collection接口另外一个常用子接口,Set接口描述的是一种比较简单的集。集合中的对象并不按特定的方式排序,并且不能保存重复的对象,也就是说Set接口可以存储一组唯一、无序的对象。
1.1set集合特点:
1.无索引:不能通过索引index这个参数把元素找出来,无 Object get(int index)
2.不可重复
它的实现类:
HashSet :无序,
LinkedHashSet:有序
Set接口常用的实现类有HashSet。
1.底层的数据接口存储(哈希表):
数组+单向链表/红黑树 (当链表节点数>8,同时数组元素数量>=64,会把链表转换为红黑树)
2.查询效率快,增删速度也不慢
3.无索引: 指的是不能通过索引来获取元素: 返回值Object get(int index)
4.元素不可重复:存储元素不可重复依赖我们元素所属类的hashCode()和equals()
5.无序:不保证存入和取出的顺序一致
6.线程不安全,但效率贼高
HashSet的底层是靠HashMap支持
HashSet实现set接口,由HashMap支持
public HashSet() {
map = new HashMap<>();
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
此构造方法 :没有添加元素时,是一个空集合,当添加第一个元素时,构造一个默认初始容量(16)和默认加载因子(0.75)的空HashMap集合
java.util.HashMap<K,V>集合:是一个双列集合,一次存储一对元素
问题来了?
HashSet存储数据一次存一个,但是HashMap一次存储一对,内部HashSet集合如何把一个元素变成一对?
其实,当使用HashSet集合存储元素时,内部的HashMap会给这个数据绑定一Object类型的常量,并且该常量在HashSet中去取不到(这样就借助了HashMap集合实现了HashSet的集合功能)
public boolean add(E e) {
return map.put(e, PRESENT)==null; //PRESENT就是一个object常量
}
//内部的成员常量
private static final Object PRESENT = new Object();
总结:
HashSet的集合对象在调用add(“a”) ----->HashMap集合对象put(“a”,new Object)
hashCode()方法详解
此方法是我们Object提供的本地方法
什么本地方法
native关键字修饰的方法称之为本地方法,类似于接口,不过接口的方法都是用abstract修饰的,不同在于我们本地方法虽然也看不见方法体(实现体),但是实际上是jvm在加载时调用底层实现的。本地方法的实现体不是由java代码写的而可能由别的语言去写,如C语言,(可以这么理解,连接java代码和其他语言实现的代码入口)
- 通俗点就是:我们Java去实现本地的文件读写功能,实际上调用windows系统锁实现的功能,通过本地方法去调用C语言或C++代码
public native int hashCode();
分析:该方法的作用返回该对象的哈希码值,其实就是一个int类型数据
native修饰的此方法,我们就可以理解为根据系统资源计算一个哈希值(或者我们可以理解通过地址值来计算)
此方法支持为了提高哈希表的性能
public static void main(String[] args) {
Object object1 = new Object();
int h1 = object1.hashCode();
System.out.println(h1);//1163157884 十进制
int h2=object1.hashCode();
System.out.println(h2);//1163157884
/*
* 注意:
* 1.同一个对象多次调用hashCode()方法,返回给我们的系统计算的哈希码值是相同的
* */
Object object2 =new Object();
int h3=object2.hashCode();
System.out.println(h3);//1956725890
/*
* 注意点2:
* Object类的hashCode方法,根据系统资源计算出的哈希码值(就是一个int类型数字),只要new对象,哈希值就是不同的
*
* */
/*
* toString():返回给我们地址值
*
* 所以这个方法我们从本质就可以看作返回给我们的哈希值
*
* public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
此方法先拿hashCode的值,然后转换为16进制的字符串
}
*
* */
System.out.println(object1.toString());//java.lang.Object@4554617c 4554617c
System.out.println(object2.toString());//java.lang.Object@74a14482 74a14482
/*
* 注意3.
* 如果自定义类没有覆盖Object类的hasCode(),默认调用Object类的hasCode方法
* 根据系统资源计算一个哈希值(int 数字),只要new对象,哈希值不同
* */
}
public static void main(String[] args) {
//定义char的字符数组
char[] chars={'a','b','c'};
//然后将字符数组转换为字符串
//通过构造方法实现,还有其他方法
String s1 =new String(chars);
String s2 =new String(chars);
//获取hashCode值
int h4 = s1.hashCode();
int h5 =s2.hashCode();
System.out.println(h4);//96354
System.out.println(h5);//96354
/*
*
*因为s1和s2通过new方式,创建的,但是发现h4和h5的哈希码值相同,因为String类自己重写覆盖了
* Object类中的hasCode方法
*
* 4.根据对String类的哈希值的测试,发现String重写覆盖了Object类中的hashCode()方法
* 这个hashCode的值通过根据字符串的字符的ASCII码值按照一定算法,计算一个哈希值
* 既然是算法就存在安全隐患
*
* 安全隐患:
* 假设:字符串的哈希值,根据字符串中每个字符的阿斯克码值,简单相加而来
* "abc": 97+98+99=294
* "cab": 99+97+98=294
*
*
* 总结:
* 问题1
* 哈希值不同,能否说明两个字符串内容一定不同?
* 必须的,一定,绝对不同
*
* 问题2:
* 哈希值相同,能否说明两个字符串的内容一定相同?
*
* 不能说明
* 继续调用equals方法
* 如果equals()返回:true说明两个字符串内容相同
* 如果返回fasle,则说明字符串内容不同
*
* */
System.out.println("重地".hashCode());//1179395
System.out.println("通话".hashCode());//1179395
}
/* String 的哈希值计算
* 成员变量
* private int hash; // Default to 0 创建string变量,这个hash=0
* public int hashCode() {
*
* int h = hash; //默认是0;
*
* //这个value就是{'a','b','c'}
* if (h == 0 && value.length > 0) {
* char val[] = value;
*
* for (int i = 0; i < value.length; i++) {
* h = 31 * h + val[i]; //val[i]字符,对应的阿斯克码值
* }
* hash = h;
* }
* return h;
* }
*
*
* 计算hashCode值得算法:h = 31 * h + val[i];
* 第一次循环:h=31*0+97=97;
* 第二次循环:h=31*97+98=3105;
* 第三次循环:h=31*3105+99=96354
*/
public class Test3 {
public static void main(String[] args) {
//定义char的字符数组
char[] chars={'a','b','c'};
String s1 =new String(chars);
int h4 = s1.hashCode();
System.out.println(h4);
}
public class Test2 {
public static void main(String[] args) {
HashSet hashSet =new HashSet();
hashSet.add("abc");//96354
hashSet.add("重地");//1179395
hashSet.add("通话");//1179395
hashSet.add("abc");
System.out.println(hashSet);
//abc的索引位置
System.out.println(96354%16);
System.out.println(1179395%16);
}
}
hashSet.add(“abc”)
add方法会调用"abc".hashCode方法计算哈希值(元素在集合中的存储位置)哈希值96354,然后在集合中找有没有96354哈希值的元素,发现没有,就会把 "abc"存储到集合中。
96354%16=2
hashSet.add(“重地”)
add方法会调用"adb".hashCode方法计算哈希值(元素在集合中的存储位置)哈希1179395,然后在集合中找有没有1179395哈希值的元素,发现没有,就会把"重地"存储到集合中。
1179395%16=3
hashSet.add(“通话”)
add方法会调用"通话".hashCode方法计算哈希值(元素在集合中的存储位置)哈希1179395,然后在集合中找有1179395哈希值的元素,返现有,就会调用"通话".equals()和当前哈希值相同的元素,进行比较,返回false,把"通话"存储到集合中,并且挂在"重地"元素下面
*1179395%16=3
hashSet.add(“abc”);
add方法会调用"abc".hashCode方法计算哈希值(元素在集合中的存储位置)
哈希值96354,然后在集合中找有没有96354哈希值的元素,发现有,接着调用 “abc”.equals(),与哈希值相等的元素做比较,这时返回的结果true,则证明内容相同不给存储
例
存储自定义对象,并且保证元素的唯一性
package Test1;
/**
* @作者:Xem626
* @date: 2022/7/20 20:03
* @TODO
*/
public class Student {
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 Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
}
package Test1;
import java.util.HashSet;
/**
* @作者:Xem626
* @date: 2022/7/20 20:03
* @TODO
*/
public class Test3 {
public static void main(String[] args) {
HashSet<Student> students = new HashSet<>();
//创建学生
Student student1=new Student("柳岩",28);
Student student2=new Student("林青霞",28);
Student student3=new Student("王祖贤",28);
Student student4=new Student("林青霞",28);
Student student5=new Student("范冰冰",28);
Student student6=new Student("林青霞",43);
//添加元素
students.add(student1);
students.add(student2);
students.add(student3);
students.add(student4);
students.add(student5);
students.add(student6);
for (Student student :students){
System.out.println(student.getName()+"-----------"+student.getAge());
}
}
}
这里有重复
目前是不符合我的要求的:因为我们知道HashSet底层依赖的是hashCode()和equals()方法。
而这两个方法我们在学生类中没有重写,所以,默认使用的是Object类。
这个时候,他们的哈希值是不会一样的,根本就就不会继续判断,执行了添加操作。
要在自定义存储的类中覆写,hashCode()和equals(),可以解决
@Override
public boolean equals(Object object){
if (this==object) {
return true;
}
if (object==null) {
return false;
}
if (this.getClass()!=object.getClass()) {
return false;
}
Student student = (Student) object;
if (age!=student.age) {
return false;
}
if (name==null) {
if (student.name!=null) {
return false;
}
}else if (!name.equals(student.name)) {
return false;
}
System.out.println("this.name"+this.name+"----"+"student"+student.name);
return true;
自动生成:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
2.使用HashSet类动态存储数据
加入现在需要很多数据总查找某个数据,LinkedList类就无需考虑了,它的数据结构决定了它的查找效率低下。如果使用ArrayList类,在部知道数据的索引且需要全部遍历的情况下,效率一样很低下。为此Java集合框架提供了一个查找效率高的集合类HashSet。HashSet类实现了Set接口,是使用Set集合时最常用的一个实现类。HashSet。HashSet类实现了Set接口,是使用Set集合时最常用的一个实现类。
HashSet集合的特点:
1).集合内的元素是无序排列
2).HashSet类是非线程安全
3)允许集合元素为null。
HashSet类的常用方法
方法 | 说明 |
---|---|
boolean add(Object o) | 如果Set中尚未包含指定元素o,则添加指定元素o |
void clear() | 从set中移除所有的元素 |
int size() | 返回Set中的元素的数量(Set的容量) |
boolean isEmpty() | 如果Set不包含任何元素,则返回true |
boolean contains(Object o) | 如果Set包含指定元素o,则返回true |
boolean remove(Object o) | 如果指定元素o存在于set中,则将移除 |
例4:
需求:使用HashSet类的常用方法存储操作新闻标题信息,并返回遍历集合
实现步骤:
(1).创建HashSet对象,并添加数据
(2).获取新闻标题的总数
(3).判断集合中是否包含汽车新闻标题
(4).移除对象
(5).遍历集合
(6).判断集合是否为空
package Test1;
import java.util.HashSet;
/**
* @作者:Xem626
* @date: 2022/7/12 21:17
* @TODO
*/
public class Test4 {
public static void main(String[] args) {
//创建HashSet集合对象
HashSet hashSet=new HashSet();
NewTitle newTitle=new NewTitle(1,"汽车","管理员");
NewTitle newTitle1=new NewTitle(2,"高考","管理员");
hashSet.add(newTitle);
hashSet.add(newTitle1);
System.out.println("获取新闻标题总数:"+hashSet.size());
//判断是否含有汽车标题
System.out.println("汽车标题的新闻是否存在:"+hashSet.contains(newTitle));
//移除对象
hashSet.remove(newTitle1);
System.out.println("高考标题是否存在:"+hashSet.contains(newTitle1));
for(Object o:hashSet){
NewTitle title=(NewTitle) o;
System.out.println(title.getName());
}
System.out.println("-------------------------");
hashSet.clear();
for(Object o1:hashSet){
NewTitle title=(NewTitle) o1;
System.out.println(title.getName());
}
NewTitle newTitle2=new NewTitle(1,"汽车","管理员");
NewTitle newTitle3=new NewTitle(2,"高考","管理员");
hashSet.add(newTitle2);
hashSet.add(newTitle3);
//判断集合是不是空
boolean empty = hashSet.isEmpty();
System.out.println(empty);
}
}
3.LinkedHashSet 是HashSet子类
和hashSet方法基本一样,唯一区别存取取出有序
例:
1.底层的数据结构:
数组+双向链表/红黑树(当链表节点数>8同时数组元素>=64时,会把链表变成红黑树)
2.查询速度快,增删速度也不慢
3.无索引:指的是不能通过索引来获得元素
4.元素不可重复:依赖所属类的hashCode()和equals()方法
5.线程不安全,不同同步,但是效率高
6有序的:怎么存就是怎么取(因为加了一个链表)
public class Test4 {
public static void main(String[] args) {
//创建一个集合对象
LinkedHashSet linkedHashSet =new LinkedHashSet();
linkedHashSet.add("hello");
linkedHashSet.add("java");
linkedHashSet.add("world");
for (Object o:linkedHashSet){
String s= (String) o;
System.out.println(s);
}
}
}
4.TreeSet
能够按照对元素某种顺序排列
排列顺序有两种方式
- 1.自然排序
- 2.比较器排序
- Tree集合的特点:排序和唯一
public class Test {
public static void main(String[] args) {
TreeSet treeSet =new TreeSet();
//添加元素
treeSet.add(20);
treeSet.add(18);
treeSet.add(23);
treeSet.add(22);
treeSet.add(17);
treeSet.add(24);
treeSet.add(19);
treeSet.add(18);
treeSet.add(24);
for (Object o:treeSet){
int i = (int) o;
System.out.println(o);
}
}
}
TreeSet存储自定义对象时,要保证排序和唯一
- 1.没有告诉怎么去排序 自然,按照年龄大小来排序
- 2.元素什么情况,唯一我也没告诉它成员变量值相同就是同一元素
package Test1;
import java.util.Objects;
/**
* @作者:Xem626
* @date: 2022/7/20 20:03
* @TODO
*/
public class Student implements Comparable<Student> {
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 Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
@Override
public int compareTo(Student o) {
// 主要条件 姓名的长度
int num = this.name.length() - o.name.length();
// 姓名的长度相同,不代表姓名的内容相同
int num2 = num == 0 ? this.name.compareTo(o.name) : num;
// 姓名的长度和内容相同,不代表年龄相同,所以还得继续判断年龄
int num3 = num2 == 0 ? this.age - o.age : num2;
return num3;
}
}
package Test1;
import java.util.TreeSet;
/**
* @作者:Xem626
* @date: 2022/7/20 20:32
* @TODO
*/
public class Test6 {
public static void main(String[] args) {
TreeSet treeSet =new TreeSet();
//创建学生
Student student1=new Student("lingqingxia",27);
Student student2=new Student("zhangguorong",29);
Student student3=new Student("wanglihong",23);
Student student4=new Student("lingqingxia",27);
Student student5=new Student("fanbingbing",22);
Student student6=new Student("wuqilong",40);
Student student7=new Student("fengqingy",22);
treeSet.add(student1);
treeSet.add(student2);
treeSet.add(student3);
treeSet.add(student4);
treeSet.add(student5);
treeSet.add(student6);
treeSet.add(student7);
//for遍历
for (Object o :treeSet){
Student student = (Student) o;
System.out.println(student.getName()+"---"+student.getAge());
}
}
}
compareTo重写:
@Override
public int compareTo(Student o) {
// 主要条件 姓名的长度
int num = this.name.length() - o.name.length();
// 姓名的长度相同,不代表姓名的内容相同
int num2 = num == 0 ? this.name.compareTo(o.name) : num;
// 姓名的长度和内容相同,不代表年龄相同,所以还得继续判断年龄
int num3 = num2 == 0 ? this.age - o.age : num2;
return num3;
}
按照姓名的长度来排序
TreeSet集合保证元素排序和唯一性
唯一性:是根据比较的返回是否是0来决定
排序:
1.自然排序(元素具备比较性)
让元素所属类实现自然排序接口Comparable
2.比较器排序
让集合的构造方法接收一个比较器接口的子类对象
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//姓名长度
int num =s1.getName().length()-s2.getName().length();
//姓名内容
int num2=num==0?s1.getName().compareTo(s2.getName()):num;
//年龄
int num3 =num2==0?s1.getAge()-s2.getAge():num2;
return num3;
}
});
例:
编写一个程序,获取10个1-20的随机数
分析:
1.创建随机对象
2创建HashSet集合
3.判断集合的长度是不是小于10
是:就继续创建一个随机数添加
否,不用放
4.遍历
public class Test1 {
public static void main(String[] args) {
Random random =new Random();
//创建集合对象
HashSet hashSet =new HashSet();
while (hashSet.size()<10){
int num =random.nextInt(20)+1;
hashSet.add(num);
}
for (Object o:hashSet){
int i = (int) o;
System.out.println(i);
}
}
}