java集合框架(一)—— Set<E>

一、 整体概述

目录

一、 整体概述

二、HashSet、LinkedHashSet、TreeSet 的底层原理

1.  前置知识

2.  HashSet 集合的底层原理 

 3. LinkedHashSet的底层逻辑

4. TreeSet的底层逻辑

5.  总结

6.  注意事项:集合的并发修改异常


1. 特点:无序:数据的添加顺序和获取出的顺序不一样; 不重复; 无索引

  • HashSet: 无序、不重复、无索引
  • LinkedHashSet: 有序、 不重复、无索引
  • TreeSet:  排序、不重复、无索引
	public static void main(String[] args) {
	    //Set<Integer> set = new HashSet<>();//多态写法  无序、不重复、无索引
	    //Set<Integer> set = new LinkedHashSet<>();//多态写法  有序、 不重复、无索引

	    Set<Integer> set = new TreeSet<>(); //多态写法  排序、不重复、无索引
	    set.add(999);
	    set.add(666);
	    set.add(666);
	    set.add(888);
	    set.add(999);
	    set.add(888);
	    System.out.println(set); //[666, 888, 999]
	}
	    

tip: Set 要用到的常用方法,基本上就是Coollection 提供的 


二、HashSet、LinkedHashSet、TreeSet 的底层原理

1.  前置知识

     哈希值:

  • 就是一个int类型的数据,java里面每一个对象都有一个哈希值
  • java中所有对象都可以调用Object类提供的hashCode方法,返回对象自己的哈希值
  • 语法: public int hashCode()
  • 同一个对象多次调用hashCode方法返回的哈希值都一样
  • 不同的对象,哈希值可能相同或不同  int( -21亿多 ~ 21亿多 )   45亿个对象
	public static void main(String[] args) {
	    Student s1 = new Student("Jam", 20, 175);
	    Student s2 = new Student("Sam",26,180);
	
	    System.out.println(s1.hashCode()); //990368553
	    System.out.println(s2.hashCode()); //1096979270
	
	    String str1 = new String("abc");
	    String str2 = new String("acD");
	    System.out.println(str1.hashCode()); //96354
	    System.out.println(str2.hashCode()); //96354
	}

 

2.  HashSet 集合的底层原理 

  • 基于哈希表实现
  • 哈希表是一种增删改查数据,性能比较好的数据结构
  • 哈希表:JDK8之前,哈希表 = 数组加链表  JDK8之后,哈希表 = 数组 + 链表 + 红黑树
  • JDK8之前,Set<String> set = new HashSet<>();   set.add("数据1");  ——创建一个默认长度是16的数组 ,默认加载因子是 0.75,数组名是table;加载因子是一个介于0和1之间的浮点数。默认情况下,Java的 HashMap 的加载因子是0.75,这意味着:当 HashMap 中的元素数量达到其容量(capacity)的75%时,HashMap 会进行扩容操作,扩容操作通常是将当前的容量增加到原来的两倍,并重新散列所有元素到新的容量上。一个较低的加载因子可以减少哈希冲突,提高查找效率,但会增加内存的使用。相反,一个较高的加载因子可以减少内存的使用,但可能会增加哈希冲突,降低查找效率。
  • 数据存入流程:
  1. 使用元素的哈希值对数组长度求余计算出应存入的位置
  2. 判断当前位置是否为null, 如果是null的话直接存入
  3. 如果不是null,表示有元素,则调用equals方法比较,相等则不存;不相等则存入数组
  • JDK8之前,新元素存入数组,占老元素位置,老元素挂下面; JDK8开始之后,新元素直接挂老元素下面
  • 为什么添加的元素无序,不重复,无索引 ? 因为存入数据的时候是根据其哈希值来计算其存入的位置,所以不同对象存入数据的顺序很随机,数据大的可能在后面,所以HashSet里面的元素无序;元素每次会用equals判断,所以不重复;添加索引没有意义,因为无序,所以没办法根据添加顺序查找到对应数据。
  • JDK8开始HashSet集合的底层原理,基于哈希表:数组+链表+红黑树  当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树
  • 了解一下数据结构(树)

  • 如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode( )equals( )方法
  • 	public static void main(String[] args) { 
    		Set<Student> students = new HashSet<>();
    		Student s1 = new Student("Jack", 20, 175);
    		Student s2 = new Student("Sam",26,180);
    		Student s3 = new Student("Jam",25,158);
    		Student s4 = new Student("Jam",25,158);
    		
    		//如果没有重写hashCode和equals方法,则会不一样
    		System.out.println(s3.hashCode()); 
    		System.out.println(s4.hashCode());
    		
    		students.add(s1);
    		students.add(s2);
    		students.add(s3);
    		students.add(s4);
    		System.out.println(students);
    	 }
    
  • 在Student类里面重写

  • 	@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 && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);
    	}
    	
    	@Override
    	public int hashCode() {
    	    //姓名 年龄 身高来计算哈希值的
    	    return Objects.hash(name, age, height);
    	}
    

 

 3. LinkedHashSet的底层逻辑

  • 有序,不重复,无索引
  • 基于哈希表实现(数组、链表、红黑树),但是每个元素都额外多了一个双链表的机制记录它前后元素的位置

 

 

4. TreeSet的底层逻辑

  • 特点:不重复、无索引、可排序(默认升序排序,按照元素大小,由小到大)
  • 底层是基于红黑树实现的排序
  • tip: 对于数值类型:Integer ,Double 默认按数值本身大小进行升序排序;对于字符串类型:默认按照首字母的编号升序排序;对于自定义类型Student对象,是无法直接排序的
  • 自定义排序规则:方法一  让自定义类实现Comparable接口,重写里面的compareTo方法来指定比较规则;方法二:通过调用TreeSet 集合有参构造器,可以设置Comparator对象(比较器对象,用于指定比较规则)见下图:   

 

	public static void main(String[] args) {
	    Set<Integer> s1 = new HashSet<>();
	    s1.add(6);
	    s1.add(4);
	    s1.add(7);
	    s1.add(4);
	    s1.add(5);
	    System.out.println(s1); //[4, 5, 6, 7]
	
	    System.out.println("----------------------------------");
	    //TreeSet就近选择自己自带的比较器对象进行排序
	    Set<Student> students = new TreeSet<>((o1, o2) -> Double.compare(o1.getHeight(),o2.getHeight()));
	    students.add(new Student("小红",25,165.8));
	    students.add(new Student("Mike",22,169.8));
	    students.add(new Student("喜羊羊",22,156.20));
	    students.add(new Student("梅艳芳",27,175.3));
	    System.out.println(students);
	    //默认先执行TreeSet自带的比较器
	    //[Student{name='喜羊羊', age=22, height=156.2}, Student{name='小红', age=25, height=165.8}, Student{name='Mike', age=22, height=169.8}, Student{name='梅艳芳', age=27, height=175.3}]
	}

在Student类 继承 Comparable<Student>

 @Override
    public int compareTo(Student o) {
        //左边比右边大 返回正整数
        //左边比右边小 返回负整数
        //左右相等 返回0
        return this.age - o.age;
    }

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

 

5.  总结

  • 如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?

ArrayList 集合(有序,可重复,有索引)基于数组 (常用)

  • 希望记住元素添加顺序,且增删首尾数据比较多?

LinkedList 集合(有序,可重复,有索引) 底层是双链表

  • 不在意元素顺序,也没有重复元素,希望增删改查比较快?

HashSet(无序,不重复,无索引)基于哈希表 (常用)

  • 记住元素添加顺序,没有重复元素,增删改查快

LiinkedHashSet (有序,不重复,无索引) 基于哈希表和双链表

  • 对元素排序,没有重复元素,增删改查快

TreeSet 基于红黑树

 

6.  注意事项:集合的并发修改异常

  • 使用迭代器遍历集合时,又同时删除集合中的数据,程序就会出现并发修改异常的错误
  • 由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误(没有能够拿到迭代器对象)
  • 怎么保证遍历集合同时删除数据时不出bug?
  1. 使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。
  2. 如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做 i -- 操作。

 感谢观看~ 有错误请指出

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值