HashSet,LinkedHashSet详解

一.集合体系结构:


二.Set系列集合:(注:Set是接口,不能实例化,只能创建实现类的对象)

1.特点:

  • 无序:存和取的顺序有可能不一致:

    例如:

    package com.itheima.a06mySet;
    ​
    import java.util.HashSet;
    import java.util.Set;
    ​
    public class A01_SetDemo1 {
        public static void main(String[] args) {
            //1.创建一个Set集合的对象
            Set<String> s=new HashSet<>();
    ​
            //2.添加元素
            /* 如果当前元素是第一次添加,那么可以添加成功,返回true
               如果当前元素是第二次添加,那么添加失败,返回false
               添加失败的元素在遍历集合时不会出现
             */
            s.add("张三");
            s.add("张三");
            s.add("李四");
            s.add("王五");
    ​
            //3.打印集合
             //Set集合有无序性,先存的张三,但打印时先出的李四
            System.out.println(s);//[李四, 张三, 王五]
    ​
        }
    }
    ​

  • 不重复:集合中的元素不能重复-->可用于数据的去重:

    例如:

    package com.itheima.a06mySet;
    
    import java.util.HashSet;
    import java.util.Set;
    
    public class A01_SetDemo1 {
        public static void main(String[] args) {
            //1.创建一个Set集合的对象
            Set<String> s=new HashSet<>();
    
            //2.添加元素
            /* 如果当前元素是第一次添加,那么可以添加成功,返回true
               如果当前元素是第二次添加,那么添加失败,返回false
               添加失败的元素在遍历集合时不会出现
             */
            boolean b1=s.add("张三");
            boolean b2=s.add("张三");
            boolean b3=s.add("王五");
    
            //3.打印集合
            System.out.println(b1);//true
            System.out.println(b2);//false
            System.out.println(b3);//true
            System.out.println(s);//[张三, 王五]
    
        }
    }

  • 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素;

2.Set集合的实现类:

  • HashSet:无序,不重复,无索引

  • LinkedHashSet:有序,不重复,无索引

  • TreeSet:可排序,不重复,无索引

3.Set接口中的方法基本上与Collection的API一致:

1)Collection中的方法:

2)遍历字符串练习:

存储字符串并遍历:利用Set系列的集合,添加字符串,并使用多种方式遍历。

  • 迭代器

    例如:

    package com.itheima.a06mySet;
    
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Set;
    
    public class A01_SetDemo1 {
        public static void main(String[] args) {
            //1.创建一个Set集合的对象
            Set<String> s=new HashSet<>();
    
            //2.添加元素
            s.add("张三");
            s.add("张三");
            s.add("李四");
            s.add("王五");
    
            //3.打印集合
            Iterator<String> it=s.iterator();
            while (it.hasNext()){
                String str= it.next();
                System.out.print(str+",");
            }
    
            //运行结果为李四,张三,王五,
    
        }
    }

  • 增强for

    例如:

    package com.itheima.a06mySet;
    
    import java.util.HashSet;
    import java.util.Set;
    
    public class A01_SetDemo1 {
        public static void main(String[] args) {
            //1.创建一个Set集合的对象
            Set<String> s=new HashSet<>();
    
            //2.添加元素
            s.add("张三");
            s.add("张三");
            s.add("李四");
            s.add("王五");
    
            //3.打印集合
            for (String str : s) {
                System.out.print(str+",");
            }
    
            //运行结果为李四,张三,王五,
    
        }
    }
    

  • Lambda表达式

    例如:未改写为Lambda表达式前:

    package com.itheima.a06mySet;
    
    import java.util.HashSet;
    import java.util.Set;
    import java.util.function.Consumer;
    
    public class A01_SetDemo1 {
        public static void main(String[] args) {
            //1.创建一个Set集合的对象
            Set<String> s=new HashSet<>();
    
            //2.添加元素
            s.add("张三");
            s.add("张三");
            s.add("李四");
            s.add("王五");
    
            //3.打印集合
            s.forEach(new Consumer<String>() {
                @Override
                public void accept(String str) {
                    System.out.print(str+",");
                }
            });
    
            //运行结果为李四,张三,王五,
    
        }
    }
    

    改写为Lambda表达式后:

    package com.itheima.a06mySet;
    
    import java.util.HashSet;
    import java.util.Set;
    
    public class A01_SetDemo1 {
        public static void main(String[] args) {
            //1.创建一个Set集合的对象
            Set<String> s=new HashSet<>();
    
            //2.添加元素
            s.add("张三");
            s.add("张三");
            s.add("李四");
            s.add("王五");
    
            //3.打印集合
            s.forEach((String str) -> {
                    System.out.print(str+",");
                }
            );
    
            //运行结果为李四,张三,王五,
    
        }
    }

  • 简化Lambda表达式:

package com.itheima.a06mySet;

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

public class A01_SetDemo1 {
    public static void main(String[] args) {
        //1.创建一个Set集合的对象
        Set<String> s=new HashSet<>();

        //2.添加元素
        s.add("张三");
        s.add("张三");
        s.add("李四");
        s.add("王五");

        //3.打印集合
        s.forEach( str ->
                System.out.print(str+",")
        );

        //运行结果为李四,张三,王五,

    }
}
 

三.HashSet底层原理:

1.HashSet集合底层采取哈希表存储数据。

2.哈希表是一种对于增删改查数据性能都较好的数据结构。

a.哈希表组成:
  • JDK8之前:数组 + 链表

  • JDK8开始: 数组 + 链表 + 红黑树

b.哈希值:

例1:如果没有重写hashCode方法,不同对象计算出的哈希值是不同的

对象Student:

package com.itheima.a06mySet;

public class Student {
    private String name;
    private int age;


    public Student() {
    }

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

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

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

测试类:

package com.itheima.a06mySet;

public class A02_HashSetDemo1 {
    public static void main(String[] args) {
        //1.创建对象
        Student s1=new Student("zhangsan",23);
        Student s2=new Student("zhangsan",23);

        //2.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
        System.out.println(s1.hashCode());//运行结果为793589513
        System.out.println(s2.hashCode());//运行结果为1313922862
    }
}
例2:如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的

对象Student:

package com.itheima.a06mySet;

import java.util.Objects;

public class Student {
    private String name;
    private int age;


    public Student() {
    }

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

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param 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);
    }

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

测试类:

package com.itheima.a06mySet;

public class A02_HashSetDemo1 {
    public static void main(String[] args) {
        //1.创建对象
        Student s1=new Student("zhangsan",23);
        Student s2=new Student("zhangsan",23);

        //2.如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
        System.out.println(s1.hashCode());//运行结果为-1461067292
        System.out.println(s2.hashCode());//运行结果为-1461067292
    }
}

例3:在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)
package com.itheima.a06mySet;

public class A02_HashSetDemo1 {
    public static void main(String[] args) {
        //在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样
        //哈希碰撞
        System.out.println("abc".hashCode());//运行结果为96354
        System.out.println("acD".hashCode());//运行结果为96354
    }
}

3.HashSet在JDK8的底层原理:

上述两个方法不重写的话在底层就利用的是地址值,意义不大。


四.HashSet的三个问题:

问题1:HashSet为什么存和取的顺序可能不一样?和遍历有关,具体看b站视频(黑马程序员Java上,阿伟的)。

问题2:HashSet为什么没有索引?因为HashSet不纯粹,有链表,红黑树和数组,带索引没 意义。

问题3:HashSet是利用什么机制保证数据去重的?利用HashCode方法(可得到哈希值,哈希值可确定当前元素添加在数组中的位置)和equals方法(比较对象内部的属性值是否相同)


五.HashSet集合练习:利用HashSet集合去除重复元素(不去重一般用ArrayList集合)

1.注:一定要重写HashCode和equals方法,不重写的话会操作地址值,地址值大多都不一样,很难做到去重

如:Student类:

package com.itheima.a06mySet;

public class Student {
    private String name;
    private int age;


    public Student() {
    }

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

    /**
     * 获取
     *
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     *
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     *
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

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

测试类:

package com.itheima.a06mySet;

import java.util.HashSet;

public class A03_HashSetDemo2 {
    public static void main(String[] args) {
        /* 需求:创建一个存储学生对象的集合,存储多个学生对象。
            使用程序实现在控制台遍历该集合。
           要求:学生对象的成员变量值相同,我们就认为是同一个对象。
         */

        //1.创建学生对象
        Student s1=new Student("zhangsan",23);
        Student s2=new Student("lisi",24);
        Student s3=new Student("wangwu",25);
        Student s4=new Student("zhangsan",23);

        //2.创建集合用来添加学生
        HashSet<Student> hs=new HashSet<>();

        //3.添加元素
        System.out.println(hs.add(s1));//运行结果为true
        System.out.println(hs.add(s2));//运行结果为true
        System.out.println(hs.add(s3));//运行结果为true
        System.out.println(hs.add(s4));//运行结果为true

        //4.打印集合
        System.out.println(hs);
         /*运行结果为[Student{name = wangwu, age = 25}, Student{name = zhangsan, age = 23},
                    Student{name = lisi, age = 24}, Student{name = zhangsan, age = 23}]
          */
    }
}

本例中没有重写HashCode方法和equals方法,此时操作地址值,导致难以做到去重。因此,对象s1和对象s4即使属性值一样,但地址值不同,因此可以添加,但没做到去重。

2.重写HashCode方法和equals方法后即可做到去重:

如:Student类:

package com.itheima.a06mySet;

import java.util.Objects;

public class Student {
    private String name;
    private int age;


    public Student() {
    }

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

    /**
     * 获取
     *
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     *
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     *
     * @param 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);
    }

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

测试类:

package com.itheima.a06mySet;

import java.util.HashSet;

public class A03_HashSetDemo2 {
    public static void main(String[] args) {
        /* 需求:创建一个存储学生对象的集合,存储多个学生对象。
            使用程序实现在控制台遍历该集合。
           要求:学生对象的成员变量值相同,我们就认为是同一个对象。
         */

        //1.创建学生对象
        Student s1=new Student("zhangsan",23);
        Student s2=new Student("lisi",24);
        Student s3=new Student("wangwu",25);
        Student s4=new Student("zhangsan",23);

        //2.创建集合用来添加学生
        HashSet<Student> hs=new HashSet<>();

        //3.添加元素
        System.out.println(hs.add(s1));//运行结果为true
        System.out.println(hs.add(s2));//运行结果为true
        System.out.println(hs.add(s3));//运行结果为true
        System.out.println(hs.add(s4));//运行结果为false

        //4.打印集合
        System.out.println(hs);
         /*运行结果为[Student{name = wangwu, age = 25}, Student{name = lisi, age = 24},
                    Student{name = zhangsan, age = 23}]
          */
    }
}

3.注:String类型和Integer类型等的数据,此时不需要手动重写HashCode方法和equals方法就能做到去重,因为Java已经写好了。


六.LinkedHashSet底层原理:具体看b站视频(黑马程序员Java上,阿伟的)

代码举例:

Student类:

package com.itheima.a06mySet;

import java.util.Objects;

public class Student {
    private String name;
    private int age;


    public Student() {
    }

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

    /**
     * 获取
     *
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     *
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     *
     * @param 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);
    }

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

测试类:

package com.itheima.a06mySet;

import java.util.LinkedHashSet;

public class A04_LinkedHashSetDemo {
    public static void main(String[] args) {
        //1.创建4个学生对象
        Student s1=new Student("zhangsan",23);
        Student s2=new Student("lisi",24);
        Student s3=new Student("wangwu",25);
        Student s4=new Student("zhangsan",23);

        //2.创建集合的对象
        LinkedHashSet<Student> lhs=new LinkedHashSet<>();

        //3.添加元素
        System.out.println(lhs.add(s1));//运行结果为true
        System.out.println(lhs.add(s2));//运行结果为true
        System.out.println(lhs.add(s3));//运行结果为true
        System.out.println(lhs.add(s4));//运行结果为false

        //4.打印集合
        System.out.println(lhs);
        /*运行结果为[Student{name = zhangsan, age = 23}, Student{name = lisi, age = 24},
                   Student{name = wangwu, age = 25}]
         */
        //注:此时存和取的顺序是一致的,先存入的s1,取的时候也就先取s1,以此类推
    }
}

七.总结:

LinkedHashSet集合比HashSet集合效率低,因为LinkedHashSet集合在哈希表的基础上又多做了一些事(操作数据顺序)。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值