聊聊集合那些事之 Set集合

Set集合

  java.util.Set接口和 java.util.List接口一样,都继承类 Collection接口,它与 Collection接口的方法基本一致,并没有对Collection集合进行功能的扩展。与 List集合不同,Set集合的特点是 元素无需且不重复。

    List集合:元素有序,元素可以重复,可以通过索引值访问集合中的元素。底层维护了一个数组,特点与数组一样。

    Set集合:元素无需,且元素不可重复,它的底层使通过 HashMap实现的,通过 hashCode和equals方法达到元素不可重复的功能。

 

Set的实现类

    Set集合有多个子类,我们常用的有 java.util.HashSetjava.util.LinkedHashSet这两个集合。

 

HashSet集合介绍

    java.util.HashSetSet接口的一个实现类,它所存储的元素是无序且不可重复的java.util.HashSet底层实现是一个java.util.HashMap。HashSet根据对象的哈希值来确定元素在集合中的位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于它的hashCode和equals方法。

下面我们来看一段代码,通过代码进行原理分析:

Set<String> set = new HashSet<String>();

// 向HashSet集合中添加元素,其中元素"123"被添加了两次
set.add("123"); // true
set.add("231"); // true
set.add("312"); // true
set.add("123"); // false

for (String str : set) {
    System.out.println(str); // 输出结果:231,123,312
}

    在上面的代码中,我们创建了一个用来存储 String字符串的HashSet集合,我们向集合中添加类两次"123",但是在我们遍历集合中元素时发现,set集合中只存在1个"123",这是因为set集合不允许出现重复的元素,如果出现重复元素,则元素添加失败。

HashSet源码解读  

  下面我们来看看 HashSet的源码,它的add方法底层是通过 HashMap.put方法实现的,有兴趣的同学可以自行去阅读更深层次的源码。

    // HashSet底层维护的是一个 HashMap对象
    private transient HashMap<E,Object> map;

    // HashSet的构造方法内部创建了一个HashMap对象
    public HashSet() {
        map = new HashMap<>();
    }
    

    /**
     * 向HashSet集合中添加元素
     *
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element <tt>e</tt> to this set if
     * this set contains no element <tt>e2</tt> such that
     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set
     * unchanged and returns <tt>false</tt>.
     *
     * 如果指定的元素不存在,则将其添加到此集合中。更正式地,如果集合中不包含e2元素,则添加指定的  
     * 元素e,使(e==null ? e2==null  e==null  e.equals(e2))。如果该集合已经包含该元素,则调用
     * 保持该集合不变并返回false。。
     *
     *
     * @param e element to be added to this set
     * @return <tt>true</tt> if this set did not already contain the specified
     * element
     */
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

     在详细说明add方法的执行原理之前,我们先来了解下什么HashSet的存储结构。

 

哈希表

    哈希表是 HashSet存储数据的存储结构,哈希表分为两个版本:

  • JDK8之前,哈希表 = 数组+链表
  • JDK8之后,哈希表 =  数组+链表 数组+红黑树

    哈希冲突:两个或以上的元素拥有相同的哈希值,这就产生了哈希冲突。

    在 JDK8之前,哈希表底层采用数组+链表的方式实现,也就是使用链表来解决哈希冲突,同一hash值的元素都存储在同一个链表中,但是当一个链表中的元素较多后,通过 key依次查找的效率会降低。

    所以在JDK8中,添加类数组+红黑树的实现方式,当链表的长度超过8时,就会将链表转换成红黑树结构,红黑树的特点就是查询速度快,它是一半一半的查找的;将链表转换成红黑树,这样就大大减少了查找时间。

    下面我们来看看 哈希表结构 的存储方式:

HashSet存储原理图(HashMap底层)

HashSet添加元素的实现原理

HashSet集合的add方法存储原理(以JDK8为例):

1、首先创建 HashSet集合时,初始化一个长度16的数组,数组中的每个元素都是一个链表结构。

2、执行 set.add(s1) 这行代码时:

  • add方法调用 s1的 hashCode()方法计算字符串"abc"的 hash值,hash值是 96354;
  • 然后在集合中查找有没有 96354这个哈希值,发现没有,那么就会直接将s1存储到集合中,并返回true。

3、执行 set.add(s2) 这行代码时:

  • add方法调用 s2的 hashCode()方法计算字符串"abc"的 hash值,hash值是 96354;
  • 然后在集合中查找有没有 96354这个哈希值,发现集合中已经存在这个hash值,这时就产生 Hash冲突了
  • 然后 s2会调用equals方法,与哈希值是 96354的元素进行内容的比较 s2.equals(s1),返回true,HashSet集合就认为这两个元素相同,就不会将s2存储到集合中,并且返回false。

4、执行 set.add("重地") 这行代码时:

  • add方法调用 “重地”的 hashCode()方法计算字符串"重地"的 hash值,hash值是1179395;
  • 然后在集合中查找有没有 1179395这个哈希值,发现没有,那么就会直接将 “重地”存储到集合中,并返回true。

5、执行 set.add("通话") 这行代码时:

  • add方法调用 "通话" 的 hashCode()方法计算字符串"通话"的 hash值,hash值是 1179395;
  • 然后在集合中查找有没有1179395这个哈希值,发现集合中已经存在这个hash值,这时就产生 Hash冲突了
  • 然后 通话“”会调用equals方法,与哈希值是 1179395的元素"重地"进行内容的比较 "重地".equals("通话"),返回false,HashSet集合就认为这两个元素不相同,就会将"通话"存储到集合中,并且返回true。

HashSet存储内容不重复的元素,前提是这个元素重写了hashCode和equals方法。

 

LinkedHashSet

    HashSet的特点是:存储的元素不重复并且没有顺序。当我们的需求是要保证存储顺序该怎么办呢?下面我们就了解下LinkedHashSet。

  java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。

      LinkedHashSet 存储结构:哈希表(数组+链表/红黑树)+ 链表;它比HashSet集合的存储结构多了一条链表,这条链表是用来记录储存顺序的,这样就保证了集合的有序性。

演示代码如下:

public class LinkedHashSetDemo {
	public static void main(String[] args) {
		Set<String> set = new LinkedHashSet<String>();
		set.add("bbb");
		set.add("aaa");
		set.add("abc");
		set.add("bbc");
        Iterator<String> it = set.iterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}
	}
}
结果:
  bbb
  aaa
  abc
  bbc
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值