• 【Java集合】HashSet源码解析以及HashSet与HashMap的区别


    HashSet

    前言

    HashSet是一个不可重复且元素无序的集合。内部使用HashMap实现。
    我们可以从HashSet源码的类注释中获取到如下信息:

    • 底层基于HashMap实现,所以迭代过程中不能保证和增加时的顺序相同。
    • add,remove,contains,size等方法的耗时性能,是不会随着数据量的增加而增加的。在不考虑Hash冲突的情况下时间复杂度都是O(1)。
    • 线程不安全的集合,如果在多线程的场景下建议使用
     //Collections#synchronizedSetCollections.synchronizedSet方法
    Set s = Collections.synchronizedSet(new HashSet(...));
    
    
    • 在迭代过程中,如果数据结构发生变化会抛出ConcurrentModificationException异常。

    组合HashMap

    先看一下HashSet的类图
    HashSet类图

    从上图中可以看出HashSet继承了AbstractSet并且实现了 Set,Cloneable,Serializable接口。

    在Java中基于基类进行创新,有两种方法。

    1. 继承的方式。继承基类,重写基类的一些方法。
    2. 组合基础类,通过调用基础类的方法,来复用基础类的能力。

    这里的HashSet使用的就是组合HashMap,优点如下:

    1. 继承表示父类是属于同一事物,而Set和Map本来表示的是两种不同的事物,所以继承关系不适用他,而且Java中的子类只能继承一个父类,后续难以扩展。
    2. 组合的话,更加灵活,可以任意的组合现有的基础类,并且可以在基础类的方法上进行扩展。且方法名可以自定义,无需和基础类保持一致。
      在Java编程思想和 effective java中也建议多用组合少用继承。

    以下是HashSet的组合实现:

    
    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    {
        static final long serialVersionUID = -5024744406713321676L;
        //将HashMap组合起来,key是HashSet的key,value是下面的Object
        private transient HashMap<E,Object> map;
    
        // HashMap中的value
        private static final Object PRESENT = new Object();
    
        /**
         * 构建一个新的,空的HashMap实例对象
         * default initial capacity (16) and load factor (0.75).
         */
        public HashSet() {
            map = new HashMap<>();
        }
    
        /**
         * 构造一个新集合类,负载因子时0.75,集合的容量由新的Collection决定
         */
        public HashSet(Collection<? extends E> c) {
            map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
            //和16比大小,如果给定的集合大小小于16,那初始容量大小就是16,如果大于16,就按照指定集合的容量
            //HashMap扩容阀值的计算公式:Map容量*0.75f。一旦达到阀值就会扩容,此处这样写使我们期望的大小比扩容阀值大1,就不会扩容
            addAll(c);
        }
    
        /**
        
         *构造一个新的空集合HashMap实例,可以指定初始容量和负载因子
         * @param      initialCapacity   the initial capacity of the hash map
         * @param      loadFactor        the load factor of the hash map
         * @throws     IllegalArgumentException if the initial capacity is less
         *             than zero, or if the load factor is nonpositive
         */
        public HashSet(int initialCapacity, float loadFactor) {
            map = new HashMap<>(initialCapacity, loadFactor);
        }
    
        /**
         *构造一个指定初始容量大小的HashMap,负载因子是默认的0.75
         * @param      initialCapacity   the initial capacity of the hash table
         * @throws     IllegalArgumentException if the initial capacity is less
         *             than zero
         */
        public HashSet(int initialCapacity) {
            map = new HashMap<>(initialCapacity);
        }
    
        /**
         * 这个构造创建的对象是LinkedHashMap可以指定初始容量大小和负载因子
         *
         * @param      initialCapacity   the initial capacity of the hash map
         * @param      loadFactor        the load factor of the hash map
         * @param      dummy             ignored (distinguishes this
         *             constructor from other int, float constructor.)
         * @throws     IllegalArgumentException if the initial capacity is less
         *             than zero, or if the load factor is nonpositive
         */
        HashSet(int initialCapacity, float loadFactor, boolean dummy) {
            map = new LinkedHashMap<>(initialCapacity, loadFactor);
        }
    

    常用方法

    add

    public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
    

    调用HashMap的put方法,PRESENT作为value放在HashMap中,如果key不存在返回null,锁着这里进行判断如果添加的key已经存在返回false,不存在代表添加成功返回true。

    contains

    public boolean contains(Object o) {
            return map.containsKey(o);
        }
    

    调用map的containsKey方法,如果找到有key=o返回true,否则返回false。

    remove

    public boolean remove(Object o) {
            return map.remove(o)==PRESENT;
        }
    

    iterator

    public Iterator<E> iterator() {
            return map.keySet().iterator();
        }
    

    通过Map使用keySet返回一个key的Iterator对象

    isEmpty

    public boolean isEmpty() {
            return map.isEmpty();
        }
    

    判断Set是不是为空,实际上是判断map中的size是不是为0

    size

    public int size() {
            return map.size();
        }
    

    返回的是map中的元素个数

    HashMap与HashSet的区别

    HashMap实现了Map接口,HashSet实现了Set接口。
    HashMap存储键值对,HashSet存储对象
    HashMap调用put方法增加键值对,HashSet调用add方法添加对象(底层的实现还是map的put)
    HashMap使用key计算对应的hashcode;
    HashSet使用对象计算hashcode值,如果两个对象的hashcode相同,两者不一定相等;如果equals方法返回true则两个对象相等。

    ==与equals的相同和不同点

    • ==判断的是两个变量或实例的地址(内存空间地址);equals方法判断的是变量或实例所指向的地址的值是不是一样的。
    • ==比较的是对象的引用,equals比较的是对象的值是否相同。

    为什么要规定重写equals方法时需要重写hashCode?

    1. 如果两个对象相等,则hashCode也一定相等。
    2. 两个对象相等,equals方法返回true。
    3. 两个对象有相同的hashcode,但是也不一定相同,因此在重写equals方法时需要重写hashCode方法,这样可以避免当equals方法返回true的时候因为没有重写hashCode方法,而导致对象的hashCode不相同,这与前面所讲的是矛盾的。hashCode默认是对堆上的对象产生独特的值,如果没有重写那么这两个对象无论如何都不相等。

    小结

    1. HashSet底层声明了一个HashMap,HashSet对他做了一层简单封装,操作HashSet的元素实际上是操作HashMap的元素。
    2. HashSet不保证存放元素的顺序,无序不可重复。
    3. HashSet允许值为null,且只有一个。
    4. 因为HashSet底层调用的是HashMap 方法,所以是线程不安全的。
  • 相关阅读:
    2020.12.7
    IDEA修改代码后不用重新启动项目即可刷新
    期中测试人口普查登记题目
    Android去掉标题头
    Android限制输入框内容
    Android:setOnItemClickListener cannot be used with a spinner报错
    Android修改app图标
    将外部sqlite3数据库导入到Android项目中
    IDEA个人常用快捷键
    css选择器
  • 原文地址:https://www.cnblogs.com/dataoblogs/p/14121930.html
Copyright © 2020-2023  润新知