Set接口

java.util.set接口继承自Collection接口,它与Collection接口中的方法基本一致,
并没有对 Collection接口进行功能上的扩充,只是比collection接口更加严格了。
set接口中元素是无序的,并且都会以某种规则保证存入的元素不出现重复。

简述其特点就是:

  1. 不允许存储重复的元素
  2. 没有索引,也没有带索引的方法,不能使用普通的for循环遍历

Set接口有多个实现类,java.util.HashSet是其常用的子类

下面介绍一下set接口的实现类HashSet

HashSet集合的介绍

  1. java.util.HashSet是set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。

  2. java.util.HashSet底层的实现其实是一个java.util.HashMap支持

  3. HashSet是根据对象的哈希值来确定元素在集合中的存储位置的,因此具有良好的存取和查找性能。

  4. 保证元素唯一性的方式依赖于:hashCode与 equals方法。

代码简单理解

import java.util.HashSet;
import java.util.Iterator; public class DemoHashSet {
public static void main(String[] args) {
// 创建set集合(HashSet)
HashSet<String> hashSet = new HashSet<>(); // 使用add方法想HashSet集合中添加元素
hashSet.add("A");
hashSet.add("B");
hashSet.add("C");
hashSet.add("D");
hashSet.add("A");
System.out.println("集合中的元素:" + hashSet);
System.out.println("=============================="); // 使用迭代器遍历集合
Iterator<String> ite = hashSet.iterator();
while (ite.hasNext()) {
System.out.println(ite.next());
}
System.out.println("==============================");
// 使用增强for循环遍历集合(不能使用普通的for循环,对HashSet集合进行遍历)
for (String s: hashSet) {
System.out.println(s);
}
}
}
输出结果:
集合中的元素:[A, B, C, D]
==============================
A
B
C
D
==============================
A
B
C
D

注意:普通for循环不能遍历HashSet集合,HashSet集合中没有重复的元素,元素的存储顺序不一致

HashSet集合存储数据的结构(哈希表)

什么是哈希表

哈希表(又称散列表),它是根据关键码值(Key - Value)而直接进行访问的数据结构。

也就是说,哈希表是通过把关键码值(Key)映射到表中一个位置来访问记录(Value),以加快查找的速度。这个映射函数叫做哈希函数(散列函数),存放记录的数组叫做哈希表。

/**
* 在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同hash值的链表都存储在一个链表里。
* 但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。
* 而JDK1.8中,哈希表存储采用数组+链表+或数组+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
*/

什么是链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

什么是红黑树

红黑树(又称对称二叉B树),是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。

什么是关联数组

是一种具有特殊索引方式的数组。不仅可以通过整数来索引它,还可以使用字符串或者其他类型的值(除了NULL)来索引它。

哈希值

了解了什么是哈希表后,简单理解一下什么是哈希值

什么哈希值

是通过一定的哈希算法,将一段较长的数据映射为较短小的二进制数据,这段小数据就是大数据的哈希值。

特点

他是唯一的,一旦大数据发生了变化,哪怕是一个微小的变化,他的哈希值也会发生变化。

作用

主要用途是用于文件校验或签名。

在Java程序中的哈希值

/**
* 在Java中哈希值:是一个十进制的整数,由系统随机给出的二进制数经过换算得到的
* 其实它就是对象的地址值,是一个逻辑地址,是模拟出来得到地址,并不是数据实际存储的物理地址
*
* 在Object类有一个方法,可以获取对象的哈希值:
* int hashCode() 返回对象的哈希码值
* hashCode()方法源码:
*            public native int hashCode();
*            native:代表该方法是调用本地操作系统的方法
*/
// 随便创建一个类
public class Person extends Object{
private String name;
private int age; public Person() {
} public Person(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;
}
}
// 用这个类创建对象,查看对象的哈希值
public class DemoHashCode {
public static void main(String[] args) {
// 创建一个p1对象,查看其哈希值
Person p1 = new Person("LeeHua", 21);
// 调用Object的hashCode方法,获取哈希值,p1的哈希值是不变的
int h1 = p1.hashCode();
System.out.println(h1); // 创建一个p2对象,查看其哈希值
Person p2 = new Person("WanTao", 20);
// 调用Object的hashCode方法,获取哈希值,p2的哈希值也是不变的
int h2 = p2.hashCode();
System.out.println(h2); // 查看p1、p2的地址值
System.out.println(p1);
System.out.println(p2);
}
}
输出结果:
1639705018
1627674070
view.study.demo18.Person@61bbe9ba
view.study.demo18.Person@610455d6

假如覆盖重写hashCode方法,所创建的对象的哈希值就会被影响

如:

public class Person1 extends Object{

    /**
* 重写hashCode方法
* @return 哈希值
*/
@Override
public int hashCode() {
return 666;
}
}
public class DemoHashCode1 {
public static void main(String[] args) {
// 创建一个p1对象,查看其哈希值
Person1 p1 = new Person1();
// 调用Object的hashCode方法,获取哈希值,p1的哈希值是不变的
int h1 = p1.hashCode();
System.out.println(h1); // 查看p1、p2的地址值
System.out.println(p1);
}
}
输出结果:
666
view.study.demo18.Person1@29a

如:我们常用的String类,它也覆盖重写了hashCode方法

public class DemoStringHashCode {
public static void main(String[] args) {
/*
String类的哈希值
(String类重写了Object类的hashCode方法)
*/
String s1 = new String("LeeHua");
String s2 = new String("WanTao");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
输出结果:
-2022794392
-1711288770

图形理解

可以看到,数组的每个位置放到一定的值,每部分值都对应一个哈希值。

当值的个数超过8个的时候,采用数组+红黑树;当值的个数不到8个的时候,采用数组+链表

如:

值1、2、3、4的哈希值都是11

值13、14、15的哈希值都是15

值a、b、c、d、e、f、g的哈希值都是89

Set集合存储元素不重复原理

原理

set集合在调用add()方法的时候,add()方法会调用元素的hashCode()方法和 equals()方法判断元素是否重复

代码举例

import java.util.HashSet;

public class DemoStringHashCode1 {
public static void main(String[] args) { HashSet<String> hashSet = new HashSet<>(); String s1 = new String("abc");
String s2 = new String("abc"); hashSet.add(s1);
hashSet.add(s2);
hashSet.add("一号");
hashSet.add("二号"); System.out.println("s1的哈希值:" + s1.hashCode());
System.out.println("s2的哈希值:" + s2.hashCode());
System.out.println("一号的哈希值:" + "一号".hashCode());
System.out.println("二号的哈希值:" + "二号".hashCode());
System.out.println("HashSet集合:" + hashSet);
}
}
输出结果:
s1的哈希值:96354
s2的哈希值:96354
一号的哈希值:640503
二号的哈希值:644843
HashSet集合:[二号, abc, 一号]

代码讲解

最初,hashSet集合是空的

hashSet.add(s1)的时候,

第一步:add()方法首先会调用s1的hashCode()方法,计算字符串"abc"的哈希值,其哈希值是,

第二步:查找集合中哈希值是中的元素,没有发现哈希值是的key

第三步:将s1存储到集合hashSet中(于是集合hashSet中存在哈希值96354,且对应这数据s1)

hashSet.add(s2)的时候

第一步:add()方法首先会调用s2的hashCode()方法,计算字符串"abc"的哈希值,其哈希值是,

第二步:查找集合hashSet中是否存在哈希值是,即哈希值冲突,

第三步:s2调用equals()方法,和集合中哈希值是对应的元素进行比较

第四步:s2.equals(s1)返回true,即哈希值是96354对应的元素已经存在,所以就不添加s2进集合了(其中:s1 = "abc",s2 = "abc")

hashSet.add("一号")的时候

第一步:调用 "一号" 的hashCode()方法,计算字符串 "一号" 的哈希值,其哈希值是,

第二步:查找集合中哈希值是中的元素,没有发现哈希值是的key,

第三步:将 "一号" 存储到集合hashSet中(于是集合hashSet中存在哈希值640503,且对应这数据 "一号")

hashSet.add("二号")的时候

第一步:调用 "二号" 的hashCode()方法,计算字符串 "二号" 的哈希值,其哈希值是,

第二步:查找集合中哈希值是中的元素,没有发现哈希值是的key,

第三步:将 "二号" 存储到集合hashSet中(于是集合hashSet中存在哈希值644843,且对应这数据 "二号")

添加完成,集合hashSet = [abc, 一号, 二号]

 HashSet存储自定义类型元素

hashSet存储自定义类型元素,那么自定义的类必须重写hashCode()方法和equals()方法,否则添加的元素可以出现重复,我们平时使用的类型,它们都重写类hashCode()方法和equals()方法。

假如不重写hashCode()方法和equals()方法

例子:

// 随便创建一个类,作为HashSet存入数据的类型
public class Person{
private String name;
private int age; @Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
} public Person() {
} public Person(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;
}
}

阿里巴巴Java Code规范会抛出警告

测试一下会出现什么情况

import java.util.HashSet;

public class Demo01PersonHashSet {
public static void main(String[] args) {
HashSet<Person> hashSet = new HashSet<>(); Person p1 = new Person("小明", 20);
Person p2 = new Person("小明", 20);
Person p3 = new Person("小红", 20); hashSet.add(p1);
hashSet.add(p2);
hashSet.add(p3);
System.out.println(hashSet);
}
}
输出结果:
[Person{name='小明', age=20}, Person{name='小明', age=20}, Person{name='小红', age=20}]

可以看到,hashSet集合里面可以存在重复的元素

重写hashCode()方法和equals()方法

还是上面这个例子:

在Person类里面添加要重写hashCode()、equals()方法的代码即可,要添加的代码如下

public class Person{
@Override
public boolean equals(Object o) {
// 参数 == 对象
if (this == o) {
return true;
}
// 传入参数为空,或者对象与参数的hashCode不相等
if (o == null || getClass() != o.getClass()) {
return false;
}
// 向下转型,把Object类型转型为Person类型
Person person = (Person) o;
// 返回 age,name
return age == person.age &&
Objects.equals(name, person.name);
} @Override
public int hashCode() {
return Objects.hash(name, age);
}
}

再次用上面的代码测试一下Person类型的数据添加是否会出现重复

输出结果:
[Person{name='小明', age=20}, Person{name='小红', age=20}]

可以看到,输出结果中,hashSet集合的元素并没有重复,因此,如果我们想要用HashSet集合存储自定义类型的数据,一定要记得覆盖重写hashCode()方法和equals()方法

Java set接口之HashSet集合原理讲解的更多相关文章

  1. Java学习(set接口、HashSet集合)

    一.set接口 概念:set接口继承自Collection接口,与List接口不同的是,set接口所储存的元素是不重复的. 二.HashSet集合 概念:是set接口的实现类,由哈希表支持(实际上是一 ...

  2. Java学习:Set接口与HashSet集合存储数据的结构(哈希表)

    Set接口 java.util.Set接口 extends Collection接口 Set接口的特点: 不允许存储重复的元素 没有索引,没有带索引的方法,也不能使用普通的for循环遍历 java.u ...

  3. Java集合 HashSet的原理及常用方法

    目录 一. HashSet概述 二. HashSet构造 三. add方法 四. remove方法 五. 遍历 六. 合计合计 先看一下LinkedHashSet 在看一下TreeSet 七. 总结 ...

  4. List、Set集合系列之剖析HashSet存储原理(HashMap底层)

    目录 List接口 1.1 List接口介绍 1.2 List接口中常用方法 List的子类 2.1 ArrayList集合 2.2 LinkedList集合 Set接口 3.1 Set接口介绍 Se ...

  5. 复习java基础第三天(集合:Collection、Set、HashSet、LinkedHashSet、TreeSet)

    一.Collection常用的方法: Java 集合可分为 Set.List 和 Map 三种体系: Set:无序.不可重复的集合. List:有序,可重复的集合. Map:具有映射关系的集合. Co ...

  6. java之Set接口(单列集合)

    Set接口概述 java.util.Set 接口和 java.util.List 接口一样,同样继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Coll ...

  7. Java 之 HashSet 集合

    一.概述 java.util.HashSet  是 Set 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致). java.util.HashSet 底层的实现是一个 ...

  8. 红黑树规则,TreeSet原理,HashSet特点,什么是哈希值,HashSet底层原理,Map集合特点,Map集合遍历方法

    ==学习目标== 1.能够了解红黑树 2.能够掌握HashSet集合的特点以及使用(特点以及使用,哈希表数据结构) 3.能够掌握Map集合的特点以及使用(特点,常见方法,Map集合的遍历) 4.能够掌 ...

  9. Java集合原理分析和知识点大杂烩(多图初学者必备!!)

    一.数据结构 ​ 数据结构就是计算机存储.组织数据的方式. ​ 在计算机科学中,算法的时间复杂度是一个函数,它定性描述了该算法的运行时间,常用O符号来表述. ​ 时间复杂度是同一问题可用不同算法解决, ...

随机推荐

  1. Pipeline & PageProcesser

    Pipeline & PageProcesser 这两部分是应该程序员自己实现的部分,因为PageProcesser关乎如何解析页面而Pipeline则是存储,推荐使用OOSpider也就是注 ...

  2. 11-28-----vertor和list使用场景

    1.vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随机访问,而不子啊胡插入和删除的效率,使用vector, 2.list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关 ...

  3. JS(JavaScript)的j进一步了解9(更新中···)

    1.动画 (1) Css样式提供了运动 过渡的属性transition  从一种情况到另一种情况叫过渡 Transition:attr  time  linear  delay: Attr 是变化的属 ...

  4. Linux 内核VLB 总线

    另一个对 ISA 的扩展是 VESA Local Bus(VLB) 接口总线, 它扩展了 ISA 连接器, 通过 添加第 3 个知道长度的槽位. 一个设备可只插入这个额外的连接器(不用插入 2 个关联 ...

  5. GetDc函数与GetWindowDC函数的区别

    GetDc函数:用于获得hWnd参数所指定窗口的客户区域的一个设备环境 GetWindowDC函数:返回hWnd参数所指定的窗口的设备环境. 获得的设备环境覆盖了整个窗口(包括非客户区),例如标题栏. ...

  6. HDU 6623 Minimal Power of Prime(数学)

    传送门 •题意 给你一个大于 1 的正整数 n: 它可以分解成不同的质因子的幂的乘积的形式,问这些质因子的幂中,最小的幂是多少. •题解 把[1,10000]内的素数筛出来,然后对于每个素$P$数遍历 ...

  7. Sql Server知识点拨

    一.Sql Server异常捕获try catch 二.集增加与修改的存储过程 三.显示某一列中有重复值的行 转载自:https://www.cnblogs.com/527289276qq/

  8. Java 学习笔记(11)——lambda 表达式

    在写Java代码的时候,如果某个地方需要一个接口的实现类,一般的做法是新定义一个实现类,并重写接口中的方法,在需要使用的时候new一个实现类对象使用,为了一个简单的接口或者说为了一个回调函数就得额外编 ...

  9. hibernate_检索策略

    一.概述 检索策略分三大块,类级别检索策略和关联级别检测策略. 类级别检索策略:get.load. 关联级别检索策略:order.getCustomer().getName() 上面这两种应该是看得懂 ...

  10. Appium中使用相对路径定位元素Xpath

    1.若页面该元素没有ID 2.找出该元素父类,并拷贝出父类的class(类名)和ID 3.点击搜索图标,进行查找 selector内容://android.widget.LinearLayout[@r ...