JavaSE学习笔记 详解Set集合中实现类:HashSet集合(附HashSet底层详解)


前面我们学习完Collection接口中子接口List中的所有内容,今天来学习下Collection集合中另外一个子接口:Set接口。

在这里插入图片描述

1. Set集合的概述

前面我们了解到List接口中存储的为有序的、可以重复的数据。
而Set接口中存储无序的、不可重复的元素。 Set接口中有三个具体实现类:HashSet,LinkedHashSet以及TreeSet。

下面总结这三个具体实现类的特点:

Set接口中三个具体实现类特点
HashSet作为Set接口的主要实现类,线程不安全的,可以存储null值
LinkedHashSet作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序进行遍历
TreeSet可以按照添加对象的指定属性,进行排序

在这里需要注意的是Set集合中没有定义额外的新的方法,使用的都是Collection接口中的方法。


2.对于Set集合中存储无序的、不可重复的数据理解

我们应经直到Set集合的特点是:存放无序的,不可重复的元素。对于无序性与不可重复性该如何进行理解呢?

这里以HashSet(HashSet为Set接口的主要实现类)为例说明:

小编是这样认为的:

无序性:不等于随机性。HashSet底层的数据结果为数组加链表加二叉树(JDK1.8)。存储的数据在底层数组中并非按照数组索引的顺序进行添加,而是根据数据的哈希值进行添加的

不可重复性:保证添加的元素按照equals()判断时,不能返回true,即:相同的元素只能添加一个。

//自定义的Student类
public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(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;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';


    }
}


//测试类
import java.util.HashSet;
import java.util.Set;

public class MyTest {
    public static void main(String[] args) {
        /* Set中存放的是无序的、不可重复的数据
        以HashSet为例说明:
        1.无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引
        的顺序进行添加,而是根据数据的哈希值进行添加的
        2.不可重复性:保证添加的元素按照equals()判断时,不能返回true,即:
        相同的元素只能添加一个

         */
        Set set= new HashSet();

        //向set集合中添加元素
        set.add(100);
        set.add(200);
        set.add(100);
        set.add("aaa");
        set.add("bbb");
        set.add("bbb");

        //向set集合中添加自定义类对象时,想要实现元素没有重复性,需要重写hashCode方法以及equals方法
        //如果不重写equals方法,每new一个对象,比较的为地址值,始终为不同的
        set.add(new Student("Mike",23));
        set.add(new Student("Kite",24));
        set.add(new Student("Jack",25));
        set.add(new Student("Mike",23));
       //对set集合进行遍历
        for (Object o : set) {
            System.out.println(o);
        }

    }
}

运行后的结果为:

在这里插入图片描述


3.HashSet底层详解(面试常问)

HashSet是Set集合的主要实现类,HashSet按照Hash算法来存储集合中元素。存在以下特点:

  • HashSet不能保证元素的顺序,元素是无序的
  • HashSet集合允许存放集合元素值为null
  • Hashset是不同步的,即线程不安全的

继承关系:

 java.util.Collection
    | java.util.AbstractCollection<E> 
        | java.util.AbstractSet<E> 
             | java.util.HashSet<E>

实现接口:

Set,Cloneable,Serializable,

在这里插入图片描述


HahSet的构造方法:

//无参构造方法,完成HashMap的创建
 public HashSet() {
            map = new HashMap<>();
        }
//指定集合转换为HashSet,完成HashMap的创建
  public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
//指定初始化大小和负载因子
 public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

 HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

通过构造方法可以看出,HashSet的底层实现是利用HashMap来实现的


HashSet的add()方法:

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

HashSet的add()方法调用HashMap的put()方法实现。

如果键已经存在,map.put()放回的是旧值,添加失败。如果添加成功,map.put方法返回的是null,HashSet.add()方法返回的true,则添加的元素可以作为map中的key。

通过例子阐述HashSet是无序的,而且不存在重复的元素:

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class MyTest {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("詹姆斯");
        set.add("科比");
        set.add("杜兰特");
        set.add("安东尼");
        set.add("欧文");
        set.add("欧文");
        set.add("詹姆斯");

        //使用迭代器进行遍历
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            System.out.print(next+" ");
        }
    }
}

运行后的结果为:

在这里插入图片描述


注意:HashSet存储元素的顺序并不是按照存入的顺序(与List集合不同),是按照哈希值来进行存放的,所以取数据也是按照哈希值来取的。

HashSet不存入重复元素的规则:使用hashCode()方法和equals()方法。

Hashset添加元素的过程:

我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值。
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即索引位置)。
判断数组该位置是否已经有元素:
    
    如果此位置上没有其他元素,则元素a添加成功。---->情况1
    如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
         如果hash值不相同,则元素添加成功。---->情况2
         如果hash值相同,进而需要调用元素所在类的equals()方法:
         equals()返回true,元素a添加失败
         equals()返回false,则元素a添加成功---->情况3


对于添加成功的情况2与情况3而言:元素a与已经存在指定索引位置上数据以索引的方式存储
JDK1.7:元素a放在数组中,指向原来的数组
JDK1.8:原来的元素在数组中,指向元素a

在这里插入图片描述
图1:hashCode值不相同的情况

图2:hashCode值相同,但equals不相同的情况

HashSet:通过hashCode值来确定元素在内存中的位置。一个hashCode位置上可以存放多个元素。当hashcode() 值相同equals() 返回为true 时,hashset 集合认为这两个元素是相同的元素.只存储一个(重复元素无法放入)。调用原理:先判断hashcode 方法的值,如果相同才会去判断equals 如果不相同,是不会调用equals方法的。


使用HashSet存储自定义对象,并尝试添加重复对象(对象的重复的判定):此时需要重写hashCode()方法以及equals()方法

//自定义的Person类
import java.util.Objects;
public class Person {
    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;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

//测试类:
import java.util.HashSet;
import java.util.Iterator;

public class MyTest {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();

        set.add(new Person("张三",23));
        set.add(new Person("赵六",24));
        set.add(new Person("王五",25));
        set.add(new Person("张三",23));
        set.add(new Person("李四",24));
        set.add(new Person("李四",24));

        Iterator<Person> iterator = set.iterator();
        while (iterator.hasNext()) {
            Person p = iterator.next();
            System.out.println(p);
        }
    }
}

运行后的结果为:
在这里插入图片描述
在判断自定义类对象是否相等时,是通过计算两者的hashCode值,和equals()方法进行的,这是作为Set集合判断不存在重复元素的根本原因。


总结

本节了解到了Set集合的特点:无序以及不允许元素的重复。对于HashSet底层实现原理进行探索,进而从底层更加深刻理解Set集合特点。

在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值