HashSet与HashMap的那点事

HashSet的底层实现

- 初探HashSet的构造方法

    private transient HashMap<E,Object> map;
    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     * 构造一个新的空集;支持的 HashMap 实例具有默认的初始容量 (16) 和加载因子 (0.75)
     */
    public HashSet() {
        map = new HashMap<>();
    }
    /**
     * Constructs a new set containing the elements in the specified
     * collection.  The <tt>HashMap</tt> is created with default load factor
     * (0.75) and an initial capacity sufficient to contain the elements in
     * the specified collection.
     * 构造一个包含指定集合中元素的新集合。这个HashMap 使用默认加载因子 (0.75) 和足以包含指定集合中的元素的初始容量创建。
     */
	public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
    /**
    * Constructs a new, empty set; the backing HashMap instance has the specified initial
    * capacity and the 	  
    * specified load factor.
    * 构造一个新的空集;后备 HashMap 实例具有指定的初始容量和指定的负载因子。
    */
	public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }
     /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * the specified initial capacity and default load factor (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);
    }

可以看到的是在 HashSet 中他的底层是基于内部的对象属性 HashMap ,底层操作的为 HashMap 对象,以 HashMap 存储元素。

-初探HashSet的添加元素方法

    private static final Object PRESENT = new Object();
	/**
     * 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>.
     *
     * @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;
    }

可以看到的是在 HashSet 中添加一个元素调用内部 map 属性的put方法时,传入的是一个 new Object() 对象。而HashSet对象中 map 中的 key 值实际对应的是 HashSet 中添加进入的 Value 元素,而 map 中 Value 对应的是 HashSet 中的 Object 对象,由于该对象是一个静态的 Object 属于 HashSet 类本身,所以每一个元素在 HashSet 对象的 map 属性下每一个 key 元素对应的 Object 都是同一个。

-由HashSet底层的存储结构带来的连锁反应

1.为什么要重新 equals 与 hashCode 方法

先谈谈 HashMap 的数据存储的底层原理吧,我们知道的一点就是,HashMap 的底层存储采用 红黑树+数组 的方式实现的。

    /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

首先我们的给 HashMap 对象添加一个元素的时候,可以从上面的 1.8 源码看到,我们有一个 key 值 与一个 value 值。在向对象中添加元素的时候,首先会将 key 进行 hash 运算,将得到的 hash 值与数组的长度进行取余得到桶下标,在数组上进行定位,如果发现桶下标有元素则会将 key 值进行 equals 对比,如果相同则替换,不同则进行尾追加。

其实通过上面的 HashSet 的底层可以发现一个问题。

如果我们没有进行对象的 hashcode 方法重写,那么就会增加出现 hash 碰撞的几率,使得系统需要做额外多余的 equals 比较,降低了执行的效率。

如果我们没有进行对象的 equals 方法重写,这就导致了一个问题,假设我们创建了多个 水杯 对象,这些 水杯 的大小规格都是相同的,同事也代表的它们的 hashcode 值必定是相同的,我们将 这些对象存储在一个 hashset 集合中由于底层调用的是 map 对象,此时会先进行 hash取余进行索引定位,但是发现,这些元素的 桶下标都定位到了同一个位置,那么此时,就会调用 equals 进行对此,发现由于没有重写 equals 返回的都是 false ,就对进行拉链法进行元素存入,并不会对元素进行覆盖。

这样就导致了非常严重的问题。

  1. 大量空间的浪费
  2. 元素保留并未覆盖,没有达到实际业务的需求
  • equals 方法的底层
    /**
     * 指示其他对象是否“等于”这个对象。
     * equals方法在非空对象引用上实现等价关系:
     * 它是自反的:对于任何非空引用值x , x.equals(x)应该返回true 。
     * 它是对称的:对于任何非空引用值x和y ,当且仅当y.equals(x)返回true时, x.equals(y)才应该返回true 。
     * 它是可传递的:对于任何非空引用值x 、 y和z ,如果x.equals(y)返回true并且y.equals(z)返回true ,则x.equals(z)应该返回true 。
     * 它是一致的:对于任何非空引用值x和y , x.equals(y)的多次调用始终返回true或始终返回false ,前提是没有修改对象上equals比较中使用的信息。
     * 对于任何非空引用值x , x.equals(null)应该返回false 。
     * Object类的equals方法实现了对象上最有区别的可能等价关系;也就是说,对于任何非空引用值x和y ,当且仅当x和y引用同一个对象( x == y的值为true )时,此方法才返回true 。
     * 请注意,每当重写该方法时,通常都需要重写hashCode方法,以维护hashCode方法的一般约定,即相等的对象必须具有相等的哈希码。
     * 参数:
     * obj - 要与之比较的参考对象。
     * 回报:
     * 如果此对象与 obj 参数相同,则为true ;否则false 。
     * 也可以看看:
     * hashCode() , java.util.HashMap
     */
    public boolean equals(Object obj) {
        return (this == obj);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值