Java强化:Set集合

发布于:2025-07-05 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、Set集合的特点

  • Set集合整体特点:属于Collection家族分支,具有无序、不重复、无索引的特点。无序指添加和获取顺序不一致,不重复即不能存相同数据,无索引即不支持通过索引操作元素。
  • HashSet特点及演示:是Set家族的代表实现类,使用多态方式创建,如Set<String> set = new HashSet<>()。向其中添加元素,重复元素会被保留一个,且元素顺序与添加顺序不同,验证了无序、不重复、无索引的特点。
  • LinkedHashSet特点:是Set的另一个实现类,与HashSet不同,它是有序的,添加元素的顺序与获取顺序一致,但同样不支持索引。
  • TreeSet特点:具有可排序的特点,默认按升序排列元素,同时也不重复、无索引。通过添加数字类型元素可演示其排序功能。
  • Set集合功能总结:Set集合的功能主要继承自Collection接口,没有额外的与索引相关的方法,常用方法如clear、size等,遍历方式与Collection一致。

二、Set底层原理

  • 哈希值的概念及特点:哈希值是int类型的随机值,可视为对象的“逻辑地址”,通过Object.hashCode()方法获取。同一对象多次调用该方法返回的哈希值相同,不同对象的哈希值大概率不同,但可能因int类型范围限制出现哈希碰撞。
  • JDK1.8前HashSet底层原理:基于“数组+链表”实现的哈希表存储。首次添加元素时创建长度为16、加载因子0.75的数组table,通过哈希值与数组长度的运算(类似求余)确定存储位置。若位置为空则直接存储;若位置已有元素,通过equals()判断是否重复,重复则不存,不重复则新元素覆盖原位置,原元素转为链表节点挂在下方。
  • JDK1.8后HashSet底层优化:引入“红黑树”优化性能。当链表长度超过8且数组长度≥64时,链表转为红黑树,利用其平衡特性提升查询效率(二分查找)。
  • 哈希表扩容机制:当哈希表元素个数超过数组长度×加载因子(16×0.75=12)时,数组扩容为原来的2倍(32),并重新分配元素位置,避免链表过长导致性能下降。
  • HashSet特点的底层原因:无序是因为元素存储位置由哈希值计算决定,与添加顺序无关;不重复是通过哈希值定位和equals()比较双重机制保证;无索引是因元素可能存储在链表或红黑树中,无法通过索引直接访问。
  • 红黑树的引入背景:链表长度过长时查询性能退化(O(n)),红黑树作为自平衡二叉查找树,查询复杂度为O(log n),能有效提升大数据量下的检索效率。
  • HashSet性能分析:增删改查性能均较好。查询时通过哈希值快速定位数组位置,若为红黑树则二分查找;添加时计算位置后直接存储或按树结构插入;删除时快速定位后直接移除。但存在耗内存(需存储哈希值、链表/树节点引用)、无索引、不能重复等局限性。

三、自定对象去重

  • Set集合去重操作的需求:当存储多个学生对象时,若多个学生对象的成员变量值相同,需只保留一个。
  • 直接使用HashSet的去重问题:新建学生对象并添加到HashSet中,发现相同内容的对象未被去重,因为不同对象的地址不同,哈希值不同,存储位置不同,Set集合认为不是重复元素。
  • HashSet去重原理:对象需先计算到同一个位置,再通过equals比较内容相同才会被认为重复,因此需重写hashCode和equals方法。
  • 重写hashCode和equals方法的原因:重写hashCode可让内容相同的对象哈希值相同,存储在同一位置;重写equals可避免不同对象哈希值相同但内容不同的情况,确保内容相同才判定为重复。
  • 重写方法的实现方式:在IDEA中可通过右键自动生成重写的hashCode和equals方法,生成的代码会基于对象的成员变量(如名字、年龄、地址、手机号等)计算哈希值和进行内容比较。
  • 重写后的去重效果:重写后,内容相同的对象哈希值相同、存储位置相同且equals比较为true,Set集合会判定为重复元素,从而实现去重,最终集合中只保留内容不同的对象。

四、LinkedHashSet集合

  • LinkedHashSet的特点:是Set集合的一个实现类,具有有序、不重复、无索引的特点。有序指的是添加顺序和取出顺序一致,先加的数据在前,后加的数据在后。
  • LinkedHashSet的底层原理:基于哈希表(数组+链表+红黑树)实现,同时每个元素添加时会通过双链表机制记录前后元素位置,通过头尾指针维护顺序,读取时从头指针开始顺双链表遍历,从而实现有序。
  • LinkedHashSet的源码分析:其内部基于LinkedHashMap实现,LinkedHashMap中存在head(头指针)和tail(尾指针),节点类型为entry,继承自HashMap的Node,除了包含哈希值、数据、next(哈希表链表指针)外,还有before和after(双链表指针),用于记录前后节点位置。
  • LinkedHashSet的性能与代价:增删改查性能好且有序,但每个节点因存储双链表指针等信息更占内存,体现了牺牲内存获取特性的设计思想。

四、TreeSet集合

  • TreeSet集合的特点及底层原理:TreeSet是一个可排序的Set集合,默认按元素大小升序排列,其底层基于红黑树实现,红黑树具有自动平衡的特性,能保证插入、删除和查找操作的高效性。
  • 不同类型元素的排序规则:数值类型(如Integer、Double)默认按数值大小升序排序;字符串默认按首字符的编号顺序排序;而自定义对象若不指定排序规则则无法排序,会报错。
  • 自定义对象排序问题及解决方案:当向TreeSet中添加自定义对象(如老师对象)时,由于TreeSet不知道按什么规则排序,会抛出异常。有两种解决方案:一是让对象所属类实现Comparable接口,重写compareTo方法,在方法中指定排序规则,例如按年龄排序时,通过比较年龄的差值来返回正整数、负整数或零;二是在创建TreeSet集合时传入Comparator比较器对象,在比较器的compare方法中定义排序规则,例如按薪水降序排序,可利用包装类的compare方法来处理。
  • 排序规则的具体实现及注意事项:实现Comparable接口时,compareTo方法的返回值决定了元素的顺序,若返回正整数则当前对象大于比较对象,排在右边,反之排在左边,返回零则视为相等,不会重复存储。实际开发中常用年龄差等一行代码实现升序排序,若要降序则调整比较顺序。使用Comparator比较器时,其优先级高于对象自身的排序规则,且在处理double类型的薪水排序时,不能直接做差后强转成整数,否则可能因精度问题导致错误,应使用包装类的compare方法来保证准确性,还可利用Lambda表达式简化比较器的代码。
  • TreeSet集合的去重机制:TreeSet集合根据排序规则判断元素是否相等,若排序规则下元素相等(如年龄相同),则只保留一个,即去重;若不想去重,可在排序规则中避免返回零,让TreeSet认为元素不等。
  • Java集合的选择与应用场景:ArrayList适合频繁根据索引查数据且不要求元素唯一的场景;LinkedList适合增删首尾数据较多的情况;HashSet适合增删改查都快且不在意元素顺序和重复的场景;LinkedHashSet能记住元素添加顺序且无重复;TreeSet用于对元素进行排序且无重复的场景。但实际开发中ArrayList和HashSet使用较多,排序也不一定非要用TreeSet,ArrayList也可通过其他方式排序。