Java集合(五)HashSet、LinkedHashSet使用及源码分析

  本系列文章:
    Java集合(一)集合框架概述
    Java集合(二)ArrayList、LinkedList使用及源码分析
    Java集合(三)Vector、Stack使用及源码分析
    Java集合(四)HashMap、Hashtable、LinkedHashMap使用及源码分析
    Java集合(五)HashSet、LinkedHashSet使用及源码分析
    Java集合(六)ArrayBlockingQueue、LinkedBlockingQueue使用及源码分析

HashSet

一、HashSet概述

  • 1、HashSet的继承关系
      简单来说,HashSet 是一个无序集合。HashSet的继承关系:
public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

  图示:

  从继承关系上来看,HashSet的特性:

  1. 继承了AbstractSet,实现了Set,可以进行Set相关操作。
  2. 间接实现了Cloneable接口,能被克隆。
  3. 间接实现了Serializable接口,支持序列化。
  • 2、HashSet的底层结构
      HashSet的底层结构是HashMap,简单来说,HashSet是value是固定值(Object PRESENT = new Object())的HashMap。

二、HashSet特点

2.1 HashSet的特点

  • 1、HashSet的底层实现是HashMap(HashSet的值存放于HashMap的key上,HashMap的value是一个统一的值)。
  • 2、HashSet中的元素无序且不能重复(从插入HashSet元素的顺序和遍历HashSet的顺序对比可以看出)。
  • 3、HashSet是线程不安全的。如果要保证线程安全,其中一种方法是将其改造成线程安全的类,示例:
	Set set = Collections.synchronizedSet(new HashSet(...));
  • 4、HashSet允许存入null

2.2 HashSet保证数据不可重复的方式

  向HashSet 中add元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较
  HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V,所以不会重复(HashMap 比较key是否相等是先比较hashcode 再比较equals )。
  hashCode()与equals()的相关规定:

  1. 如果两个对象相等,则hashcode一定也是相同的;
  2. 两个对象相等,对两个equals方法返回true;
  3. 两个对象有相同的hashcode值,它们也不一定是相等的;
  4. 综上,equals方法被重写,则hashCode方法也必须被重写;
  5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

三、HashSet使用

3.1 常用方法介绍

  • 1、构造一个新的空HashSet,默认初始容量为16,负载因子为0.75
	public HashSet()
  • 2、构造一个新的空HashSet,指定初始容量,负载因子为0.75
	public HashSet(int initialCapacity)
  • 3、构造一个新的空HashSet,指定初始容量和负载因子
	public HashSet(int initialCapacity, float loadFactor)
  • 4、添加元素
	public boolean add(E e)
  • 5、清空集合
	public void clear()
  • 6、如果此集合包含指定的元素,则返回 true
	public boolean contains(Object o)
  • 7、如果此集合不包含任何元素,则返回 true
	public boolean isEmpty()
  • 8、获取此集合中元素的迭代器
	public Iterator<E> iterator()
  • 9、删除元素
	public boolean remove(Object o)
  • 10、返回此集合中的元素数
	public int size()

2.2 常用方法使用

        //1.构造方法
		HashSet<String> hashSet = new HashSet<>();
		HashSet<String> hashSet2 = new HashSet<>(20);
		HashSet<String> hashSet3 = new HashSet<>(20,0.8f);
		
		//2.添加元素
		hashSet.add("abc");
		System.out.println(hashSet); //[abc]
		
		//3.删除所有元素
		hashSet.clear();
		System.out.println(hashSet); //[]
		
		//4.判断是否包含指定的元素
		System.out.println(hashSet.contains("1")); //false
		
		//5.判断是否为空
		System.out.println(hashSet.isEmpty()); //true
		
		//6.获取迭代器
		hashSet.add("123");
		hashSet.add("456");
		hashSet.add("789");
		hashSet.add("101");
		Iterator<String> iterator = hashSet.iterator();
		while(iterator.hasNext()) {
    
    
			System.out.print(iterator.next()+" ");  //123 101 456 789  
		}
		System.out.println();

		//7.删除元素
		hashSet.remove("789");
		hashSet.forEach(x -> System.out.print(x+" ")); //123 101 456 
		System.out.println();
		
		//8.返回此集合中的元素数
		System.out.println(hashSet.size()); //3

三、HashSet源码

  HashSet源码中最先出现的是定义了几个变量:

    //HashSet集合中的内容是通过HashMap数据结构来存储的
    private transient HashMap<E,Object> map;
    //向HashSet中添加数据,数据在上面的map结构是作为key存在的,而value统一都是
    //PRESENT,这样做是因为Set只使用到了HashMap的key,所以此处定义一个静态的常
    //量Object类,来充当HashMap的value
    private static final Object PRESENT = new Object();

3.1 构造方法

  • 1、HashSet()
    //直接 new 一个HashMap对象出来,采用无参的 HashMap 构造函数,
    //HashMap对象具有默认初始容量(16)和加载因子(0.75)
    public HashSet() {
    
    
        map = new HashMap<>();
    }
  • 2、HashSet(int initialCapacity, float loadFactor)
	//指定初始容量和加载因子,创建HashMap实例
    public HashSet(int initialCapacity, float loadFactor) {
    
    
        map = new HashMap<>(initialCapacity, loadFactor);
    }
  • 3、HashSet(int initialCapacity)
	//指定初始容量,创建HashMap
    public HashSet(int initialCapacity) {
    
    
        map = new HashMap<>(initialCapacity);
    }
  • 4、HashSet(int initialCapacity, float loadFactor, boolean dummy)
	//以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。 
    //此构造函数为包访问权限,不对外公开,实际只是对LinkedHashSet的支持。 
    //实际底层会以指定的参数构造一个空LinkedHashMap实例来实现。 
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    
    
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

  加载因子指的是能存储的元素占总的容量的比例。在 HashMap 中,能够存储元素的数量就是:总的容量*加载因子
  每当向HashSet新增元素时,如果HashMap集合中的元素大于前面公式计算的结果,就必须要进行扩容操作,从时间和空间考虑,加载因子一般都选默认的0.75。

3.2 添加元素

  当把对象加入HashSet时,HashSet会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,则覆盖旧元素。

	//将 e 作为 key,PRESENT 作为 value 插入到 map 集合中,如果 e 
	//不存在,则插入成功返回 true;如果存在,则返回false
	//本质上是调用HashMap的put方法来实现的
    public boolean add(E e) {
    
    
        return map.put(e, PRESENT)==null;
    }

3.3 删除元素

	//删除成功返回 true,删除的元素不存在会返回 false
	//本质上是调用HashMap的remove方法来实现的
    public boolean remove(Object o) {
    
    
        return map.remove(o)==PRESENT;
    }

3.4 查找元素

	//调用 HashMap 的 containsKey(Object o) 方法,找到了返回 true,找不到返回 false
	//因为HashSet的本质上是用HashMap来存储元素的,HashSet的值是HashMap中的key,所以
	//此处调用了HashMap的containsKey方法来判断
    public boolean contains(Object o) {
    
    
        return map.containsKey(o);
    }

3.5 清空集合/判断是否为空/获取HashSet元素个数

  这几个方法都是直接调用其底层实现HashMap的方法的,源码:

	//清空集合
    public void clear() {
    
    
        map.clear();
    }
	//判断是否为空
    public boolean isEmpty() {
    
    
        return map.isEmpty();
    }
	//获取集合元素个数
    public int size() {
    
    
        return map.size();
    }

3.6 迭代器

	//因为HashSet的本质上是用HashMap来存储元素的,HashSet的值是HashMap中的key,所以
	//此处调用了HashMap的keySet方法来遍历HashSet中的元素
    public Iterator<E> iterator() {
    
    
        return map.keySet().iterator();
    }

LinkedHashSet

一、LinkedHashSet概述

  • 1、LinkedHashSet的继承关系
      LinkedHashSet是有序集合,其继承关系:
public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

  图示:

  从继承关系上来看,LinkedHashSet的特性:

  1. 继承HashSet,实现Set接口,可以进行Set相关的操作。
  2. 实现了Cloneable接口,能被克隆。
  3. 实现了Serializable接口,这意味着Serializable支持序列化,能通过序列化去传输。
  • 2、LinkedHashSet的底层结构
      LinkedHashSet底层是通过LinkedHashMap来实现的,LinkedHashMap其实也就是value是固定值的LinkedHashMap。

二、LinkedHashSet特点

2.1 LinkedHashSet的特点

  1. 底层是用LinkedHashMap来实现的。
  2. 线程不安全 。
  3. 元素有序,是按照插入的顺序排序的。
  4. 最多只能存一个null。

2.2 LinkedHashSet支持按元素访问顺序排序吗

  LinkedHashSet所有的构造方法都是调用HashSet的同一个构造方法:

    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    
    
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

  然后,通过调用LinkedHashMap的构造方法初始化Map:

    public LinkedHashMap(int initialCapacity, float loadFactor) {
    
    
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

  可以看出:这里把accessOrder写死为false,所以,LinkedHashSet是不支持按访问顺序对元素排序的,只能按插入顺序排序。

三、LinkedHashSet使用

3.1 常用方法介绍

  • 1、构造一个具有默认初始容量(16)和负载因子(0.75)的LinkedHashSet
	public LinkedHashSet()
  • 2、构造一个具有指定初始容量和默认负载因子(0.75)的LinkedHashSet
	public HashSet(int initialCapacity)
  • 3、构造具有指定的初始容量和负载因子的LinkedHashSet
	public HashSet(int initialCapacity, float loadFactor)

3.2 方法使用

        //1.构造方法
		LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
		LinkedHashSet<String> linkedHashSet2 = new LinkedHashSet<>(20);
		LinkedHashSet<String> linkedHashSet3 = new LinkedHashSet<>(20,0.7f);
		
		//2.获取元素迭代器
		linkedHashSet.add("1");
		linkedHashSet.add("2");
		linkedHashSet.add("3");
		Iterator<String> iterator = linkedHashSet.iterator();
		while (iterator.hasNext()) {
    
    
			System.out.print(iterator.next()+" "); //1 2 3 
		}
		System.out.println();
		
		//3.获取元素个数
		System.out.println(linkedHashSet.size()); //3
		
		//4.是否为空
		System.out.println(linkedHashSet.isEmpty()); //false
		
		//5.是否包含某个元素
		System.out.println(linkedHashSet.contains("2")); //true
		
		//6.删除某个元素
		linkedHashSet.remove("1");
		linkedHashSet.forEach(x -> System.out.print(x+" ")); //2 3 
		System.out.println();
		
		//7.清空集合
		linkedHashSet.clear();
		System.out.println(linkedHashSet); //[]

四、LinkedHashSet源码

  LinkedHashSet源码很简单。

  • 1、LinkedHashSet(int initialCapacity, float loadFactor)
    public LinkedHashSet(int initialCapacity, float loadFactor) {
    
    
    	//调用其父类HashSet的构造器,指定初始容量和增长因子,构造一个LinkedHashMap
        super(initialCapacity, loadFactor, true);
    }
  • 2、LinkedHashSet(int initialCapacity)
    public LinkedHashSet(int initialCapacity) {
    
    
    	//调用其父类HashSet的构造器,指定初始容量,增长因子为0.75,构造一个LinkedHashMap
        super(initialCapacity, .75f, true);
    }
  • 3、public LinkedHashSet()
    public LinkedHashSet() {
    
    
    	//调用其父类HashSet的构造器,初始容量为16,增长因子为0.75,构造一个LinkedHashMap
        super(16, .75f, true);
    }

猜你喜欢

转载自blog.csdn.net/m0_37741420/article/details/112407684