HashSet与TreeSet的实现原理(通俗易懂)

 

目录

集合类体系结构

HashSet

TreeSet

 


集合类体系结构

 


HashSet

 

Hash值:哈希值是JDK根据对象的地址或者字符串或者数字计算出来的int类型的数值(object类中有一个方法可以获取对象的哈希值)

 

HashSet保证元素唯一性的源码分析:

首先进入HashSet的add方法,底层是调用了HashMap的put方法

点击进入put方法

再进入hash方法,可以看到该方法是计算key的hash值,然后再作为参数传递到putVal方法

点击进入putVal方法

简单地看下该方法的前半部分,首先第一个if判断哈希表是否为空,为空则初始化哈希表;如果不为空,则判断该哈希值在哈希表中对应数组位置是否为空,为空的话直接插入;如果不为空,则判断key值是否相同,相同的替换掉旧节点,不同则判断是否树化,树化就按树的方式进行存储,没有树化就按链表的方式进行存储,保证了元素的唯一性。

可以看出,HashSet的唯一性则是通过重写hashCode()方法和equal()方法实现的。如果插入的是String类型,Interger类型等包装类型时,可以明显地看出已经实现了唯一性,那是因为包装类的源码中已经重写了Object公共父类的hashCode()方法和equal()方法。(下面是String包装类的源码,已经重写了hashCode方法和equal方法)

 

所以如果自定义类要实现唯一性,必须重写Object公共父类的hashCode()方法和equal()方法

在自定义类中没有重写Object公共父类的hashCode()方法和equal()方法可以看到输出结果是没有去重的

package collection;

import java.util.HashSet;

public class Student {
    private String id;   //学号
    private String name;  //姓名

    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

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

   
    public static void main(String[] args) {
        Student stu0 = new Student("222","张三");
        Student stu1 = new Student("111","张三");
        Student stu2 = new Student("111","张三");
        HashSet<Student> hashSet = new HashSet<Student>();
        hashSet.add(stu1);
        hashSet.add(stu2);
        hashSet.add(stu0);
        for(Student student:hashSet)	{
            System.out.println(student.hashCode());
            System.out.println(student);
        }

    }

}

再来看看在自定义类中重写Object公共父类的hashCode()方法和equal()方法的输出结果,显然是去重的

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student student = (Student) o;
        return getId().equals(student.getId()) &&
                getName().equals(student.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getId(), getName());
    }

 

常见数据结构之哈希表:

JDK8之后,哈希表是由于“数组+链表+红黑树”实现的,数组用来存放哈希值,链表用来存放相应的元素,当元素增加一定程度的时候,链表会自动转换成红黑树,但元素减少到一定程度的时候,红黑树又会自动转换成链表。

 


TreeSet

 

TreeSet:元素有序,这里的有序不是指存储和取出的顺序,而是按照一定的规则进行排序

TreeSet():根据其元素的自然排序进行排序,需要实现Comparable接口,并覆盖其compareTo方法。

TreeSet(Comparator comparator):根据指定的比较器进行排序,则需要实现Comparator接口,并重写其compare方法。

 

先来看看TreeSet中添加String元素,不难发现已经实现了重排序和唯一性

TreeSet treeSet = new TreeSet();
        treeSet.add("111");
        treeSet.add("333");
        treeSet.add("222");
        treeSet.add("444");
        treeSet.add("111");
        System.out.println(treeSet);

  

 

 那是因为String的源码中,实现了Comparable接口,并且重写了compareTo方法

 

那么自定义类要想实现这样的效果,同样可以通过需要实现Comparable接口,并覆盖其compareTo方法。

 实现Comparable接口,并覆盖其compareTo方法

public class Customer implements  Comparable{
    private String name;
    private int age;

    public Customer(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 instanceof Customer)) return false;
        Customer customer = (Customer) o;
        return getAge() == customer.getAge() &&
                Objects.equals(getName(), customer.getName());
    }*/

    /*@Override
    public int hashCode() {
        return Objects.hash(getName(), getAge());
    }*/

    @Override
    public int compareTo(Object o) {
        Customer other = (Customer) o;

        //先按照name属性排序
        if (this.name.compareTo(other.getName())>0)
            return 1;
        if (this.name.compareTo(other.getName())<0)
            return -1;

        //再按照age属性排序
        if (this.getAge()>((Customer) o).getAge())
            return 1;
        if (this.getAge()<((Customer) o).getAge())
            return -1;

        return 0;
    }

    public static void main(String[] args) {
        TreeSet<Customer> cs = new TreeSet<>();
        cs.add(new Customer("张三",19));
        cs.add(new Customer("李四",15));
        cs.add(new Customer("张三",13));
        cs.add(new Customer("李四",12));
        for(Customer c:cs){
            System.out.println(c.name+" "+c.age);
        }
    }


}



 

 

实现Comparator接口,并覆盖其compare方法

public class CustomerComparator{
    public static void main(String[] args) {
        Set<Customer> set  = new TreeSet<Customer>(new Comparator<Customer>() {
            @Override
            public int compare(Customer o1, Customer o2) {
                if (o1.getName().compareTo(o2.getName())>0) return -1;
                if (o1.getName().compareTo(o2.getName())<0) return 1;
                if (o1.getAge()>o2.getAge())
                    return 1;
                if (o1.getAge()<o2.getAge())
                    return -1;
                return 0;
            }
        });

        Customer customer1 = new Customer("Tom",5);
        Customer customer2 = new Customer("Tom",9);
        Customer customer3 = new Customer("Tom",2);
        set.add(customer1);
        set.add(customer2);
        set.add(customer3);
        Iterator<Customer> it  = set.iterator();
        while(it.hasNext()){
            Customer customer = it.next();
            System.out.println(customer.getName()+" "+customer.getAge());
        }
    }
}

 


注:本文实属原创,如有错误,请指出。如对你有帮助,不妨点个赞呗!

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值