原文之部分某处有争论,读者谨慎阅读,后期本人再进行本文内容修正

1.简介

  • Set集合中的元素是无序的且不可重复, 如果试图把两个相同元素加入同一个Set集合中,则添加操作失败,add()方法返回false,且新元素不会被加入。

2.HasheSet底层数据结构

  • HashSet底层数据结构是哈希表,因此具有很好的存取和查找性能。
  • 哈希表:一个元素为链表的数组,综合了链表(存储速度快)和数组(查询速度快)的优点。

3.哈希表的存取原理

3.1 调用对象的hashCode()方法,获得要存储元素的哈希值。

3.2 将哈希值与表的长度(即数组的长度)进行求余运算得到一个整数值,该值就是新元素要存放的位置(即是索引值)。

  • 如果索引值对应的位置上没有存储任何元素,则直接将元素存储到该位置上。
  • 如果索引值对应的位置上已经存储了元素,则执行第3.3步。

3.3 遍历该位置上的所有旧元素,依次比较每个旧元素的哈希值和新元素的哈希值是否相同。

  • 如果有哈希值相同的旧元素,则执行第3.4步。
  • 如果没有哈希值相同的旧元素,则执行第3.5步。

3.4 比较新元素和旧元素的地址是否相同。如果地址值相同则用新的元素替换老的元素,停止比较。如果地址值不同,则新元素调用equals方法与旧元素比较内容是否相同。

  • 如果返回true,用新的元素替换老的元素,停止比较。
  • 如果返回false,则回到第3步继续遍历下一个旧元素。

3.5 说明没有重复,则将新元素存放到该位置上并让新元素记住之前该位置的元素。

4.HashSet特点

  • 无序
  • 集合中的元素值可以是null
  • hashSet不是同步的,如果多个线程同时访问一个Set,只要有一个线程修改了Set中的值,就必须进行同步处理,通常通过同步封装这个Set对象来完成同步,如果不存在这样的对象,可以使用Collections.synchronizedSet()方法完成。

5.HashSet保证唯一性:实体类中重写hashCode和equals方法

  • 实体类
public class Person {String name;int age;public Person(String name, int age) {super();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;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + "]";}
}
  • 测试代码
@Test
public void testHashSet(){Person p1 = new Person("钟梅", 25);Person p2 = new Person("王兴", 34);Person p3 = new Person("张三", 18);Person p4 = new Person("李四", 21);Person p5 = new Person("李四", 21);HashSet<Person> hashSet = new HashSet<>();hashSet.add(p1);hashSet.add(p2);hashSet.add(p3);hashSet.add(p4);hashSet.add(p5);for (Person person : hashSet) {System.out.println(person.getName()+ "----------" + person.getAge());}
}
  • 输出结果
  • 由上可以看到,结果中出现重复元素。在实体类Person中重写hashCode和equals方法:
//判断判断两个对象是否相等,对象是否存在,对象的name和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);
}//返回对象的name和age的hash值
@Override
public int hashCode() {return Objects.hash(name, age);
}
  • 重写之后不是判断两个对象hashCode是否相等,而是判断对象的name和age是否同时相等,如果同时相等则判断为同一对象,不能重复出现在集合中。
    再次遍历集合,运行结果:
  • 可以看到重复的元素已经被覆盖,保证了集合中元素的唯一性。

经验:为什么不直接用数组,而用HashSet?

  • 因为数组的索引是连续的而且数组的长度是固定的,无法自由增加数组的长度。而HashSet就不一样了,HashCode表用每个元素的hashCode值来计算其存储位置,从而可以自由增加HashCode的长度,并根据元素的hashCode值来访问元素。而不用一个个遍历索引去访问,这就是它比数组快的原因。

6.LinkedHashSet类

元素顺序是添加元素的顺序

  • LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的,也就是说当遍历集合LinkedHashSet集合里的元素时,集合将会按元素的添加顺序来访问集合里的元素。
  • 输出集合里的元素时,元素顺序总是与添加顺序一致。但是LinkedHashSet依然是HashSet,因此它不允许集合重复。

7.ThreeSet类

7.1:ThreeSet类特点

  • TreeSet可以确保集合元素处于排序状态。
  • 内部存储机制:TreeSet内部实现的是红黑树,默认整形排序为从小到大。

7.2:ThreeSet的方法

  • 与HashSet集合相比,TreeSet还提供了几个额外方法:
//如果TreeSet采用了定制顺序,则该方法返回定制排序所使用的Comparator,如果TreeSet采用自然排序,则返回null;
Comparator comparator();
//返回集合中的第一个元素;
Object first();
//返回集合中的最后一个元素;
Object last();
//返回指定元素之前的元素。
Object lower(Object e);
//返回指定元素之后的元素。
Object higher(Object e);
//返回此Set的子集合,含头不含尾;
SortedSet subSet(Object fromElement,Object toElement);
//返回此Set的子集,由小于toElement的元素组成;
SortedSet headSet(Object toElement);
//返回此Set的子集,由大于fromElement的元素组成;
SortedSet tailSet(Object fromElement);
  • 用法示例:
@Test
public void testTreeSet(){TreeSet<Integer> nums = new TreeSet<>();//向集合中添加元素nums.add(5);nums.add(2);nums.add(15);nums.add(-4);//输出集合,可以看到元素已经处于排序状态System.out.println(nums);//[-4, 2, 5, 15]System.out.println("集合中的第一个元素:"+nums.first());//集合中的第一个元素:-4System.out.println("集合中的最后一个元素:"+nums.last());//集合中的最后一个元素:15System.out.println("集合小于4的子集,不包含4:"+nums.headSet(4));//集合小于4的子集,不包含4:[-4, 2]System.out.println("集合大于5的子集:"+nums.tailSet(2));//集合大于5的子集:[2, 5, 15]System.out.println("集合中大于等于-3,小于4的子集:"+nums.subSet(-3,4));//集合中大于等于-3,小于4的子集:[2]
}
  • 输出结果:
  • 从上面的运行结果可以看出输出的集合已经按从小到大排好了,但是问题来了,只能从小到大排序吗?如果是字符对象应按该怎样的顺序排序?如果是一个对象又按怎样的顺序排序呢?遵循怎样的排序规则呢?

7.3 TreeSet的排序实现

  • TreeSet支持两种排序方法:自然排序和定制排序,在默认情况下,采用的是自然排序。

7.3.1 自然排序

  • TreeSet会调用集合元素的compareTo(Objec obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这就是自然排序。

  • 拓展:
    Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类必须实现该方法,实现接口的类就可以比较大小了。当调用一个一个对象调用该方法与另一个对象进行比较时, compareTo(Object obj)如果返回0表示两个对象相等;如果返回正整数则表明obj1大于obj2,如果是负整数则相反。

  • 案例:
    实现存储Person类的集合,排序方式,按年龄大小,如果年龄相等,则按name字符串长度,如果长度相等则比较字符。如果name和age都相等则视为同一对象。

public class Person implements Comparable<Person>{String name;int age;public Person(String name, int age) {super();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;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + "]";}@Overridepublic int compareTo(Person o) {//比较ageint num=this.age-o.age;//如果age相等则比较name长度int num1=num==0?this.name.length()-o.name.length():num;//如果前两者都相等则比较name字符串int num2=num1==0?this.name.compareTo(o.name):num1;return num2;}
}
  • 测试
@Test
public void testTreeSet(){TreeSet<Person> tree = new TreeSet<>();//向集合中添加元素tree.add(new Person("孙悟空",16));tree.add(new Person("孙悟空",17));tree.add(new Person("孙悟空",16));tree.add(new Person("唐僧",16));tree.add(new Person("沙悟净",23));tree.add(new Person("唐僧",30));//遍历System.out.println(tree);
}
  • 输出:
  • 从运行结果可以看到满足定义的排序规则。
  • 当把一个对象添加进集合时,集合调用该对象的CompareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构中找到它的存储位置。如果两个对象相等则新对象无法加入到集合中。

7.3.3 定制排序

  • TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排列。如果需要实现定制排序,例如降序排序,则可通过Comparator接口的帮助。该接口里包含一个int compare(T o1,T o2)方法,用于比较o1和o2的大小。由于Comparator是一个函数式接口,因此还可以使用Lambda表达式来代替Comparator子类对象。
@Test
public void testTreeSet2(){TreeSet<Integer> nums = new TreeSet<>((a,b)->-(a-b));//向集合中添加元素nums.add(5);nums.add(2);nums.add(15);nums.add(-4);//输出集合,可以看到元素已经处于排序状态System.out.println(nums);//[15, 5, 2, -4]
}

原文地址 https://blog.csdn.net/mashaokang1314/article/details/83721792

java中的Set集合详解相关推荐

  1. java 中map_Java Map集合详解

    Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键对象和一个值对象.其中,键对象不允许重复,而值对象可以重复,并且值对象还可以是 Map 类型的,就像数组中的元素 ...

  2. java中Freemarker list指令详解

    java Freemarker中list指令主要是进行迭代服务器端传递过来的List集合. 定义 <#list nameList as names> ${names} </#list ...

  3. java中list和map详解

    java中list和map详解 一.概叙 List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口, List下有ArrayList,Vector,LinkedL ...

  4. Java中的static关键字详解

    ** Java中的static关键字详解 ** 在一个类中定义一个方法为static,即静态的,那就是说无需本类的对象就可以调用此方法.调用一个静态方法就是 "类名.方法名" ,静 ...

  5. java中的进制输出转换_Java I/O : Java中的进制详解

    作者:李强强 上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算.这一讲,泥瓦匠带你走进Java中的进制详解. 一.引子 在Java世界里,99%的工作都是处理这高层. ...

  6. Java中的main()方法详解

    源文作者:leizhimin    源文链接:http://lavasoft.blog.51cto.com/62575/53263 源文作者版权申明: 版权声明:原创作品,允许转载,转载时请务必以超链 ...

  7. Java中的Runtime类详解

    Java中的Runtime类详解 1.类注释 /**Every Java application has a single instance of class Runtime that allows ...

  8. java中properties作用,Java中Properties的使用详解

    Java中有个比较重要的类Properties(Java.util.Properties),主要用于读取Java的配置文件,各种语言都有自己所支 持的配置文件,配置文件中很多变量是经常改变的,这样做也 ...

  9. Java 中的伪共享详解及解决方案

    转载自  Java 中的伪共享详解及解决方案 1. 什么是伪共享 CPU 缓存系统中是以缓存行(cache line)为单位存储的.目前主流的 CPU Cache 的 Cache Line 大小都是 ...

最新文章

  1. Java 8 一行代码解决了空指针问题,太厉害了...
  2. Silverlight 2 Beta 2发布
  3. linux驱动篇之 driver_register 过程分析(二)bus_add_driver
  4. 序列赋值引发的Python列表陷进
  5. bootstrap登录表单
  6. Rasa3 domain官方文档翻译
  7. 【JY】橡胶支座的简述和其力学性能计算
  8. 诗词-已然绿盈盈蓝点缀
  9. java: java mina ——基于TCP/IP、UDP/IP协议栈的通信框架
  10. 计算组合C(m,n)的计算方法(C++篇)
  11. 基于数据结构的超市会员管理系统
  12. 无代码资讯 | 数睿数据受邀参加“聚势生态·携手共赢”2021Tridium生态发展大会
  13. 中小学AI离线智能语音识别模块语音 图形化编程
  14. eval 是做什么的?
  15. 从SQL出发,程序开发的必备大法
  16. Nodejs正则表达式
  17. HBuilder X下载安装,运行微信小程序教程(官网)
  18. 如何获得最佳学习效果?
  19. 面试问题——英语26 改善环境 愿望
  20. 盘点来自工业界的GPU共享方案

热门文章

  1. java options设置_【java】后台如何处理OPTIONS请求
  2. 高性能分布式缓存Redis--- Redis底层结构和缓存原理 --- 持续更新
  3. 我的微信小程序项目进入测试啦
  4. vivoY30和Y3s的区别
  5. 内网(离线)如何安装vue
  6. C/C++学习:求平均值函数
  7. 数据结构笔记9: 图
  8. 数据库系统(DBS)的四个特点
  9. 不同API加载geojson
  10. TREK1000概述--实时定位系统