HashSet为什么要设置PRESENT

直接进入正题,HashSet是通过组合模式,使用HashMap的key是不重复的来实现HashSet中的元素是不重复的,遍历时为该map对象的keySet()即map的key集合。而HashMap通过hash函数和key实现,具体实现见HashMap,篇幅有限这里就不展开说明了。

以下为HashSet的源码,HashSet有一个全局唯一的PRESENT指向的Object对象,add的时候使用其作为map的value。

现在有疑问的地方就是,这里为什么不用null来代理PRESENT比直接使用new Object()还能节约空间?

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

其中奥秘就在以下使用PRESENT的两个方法的返回值上

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

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

两个值都是boolean类型,map.put和remove方法的处理逻辑均是,key有关联的值则返回对应的值,否则返回null。

/**
     * Removes the mapping for the specified key from this map if present.
     *
     * @param  key key whose mapping is to be removed from the map
     * @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 remove(Object key)....



/**
     * 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)...

直接上代码验证,以下代码为HashSet的源码拷贝版,只保留了主要方法。

public class MyHashSet<E>
        extends AbstractSet<E>
        implements Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
//    private static final Object PRESENT = new Object();
    private static final Object PRESENT = null;

    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public MyHashSet() {
        map = new HashMap<>();
    }

    /**
     * Returns an iterator over the elements in this set.  The elements
     * are returned in no particular order.
     *
     * @return an Iterator over the elements in this set
     * @see ConcurrentModificationException
     */
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

    /**
     * Returns the number of elements in this set (its cardinality).
     *
     * @return the number of elements in this set (its cardinality)
     */
    public int size() {
        return map.size();
    }

    /**
     * Returns <tt>true</tt> if this set contains no elements.
     *
     * @return <tt>true</tt> if this set contains no elements
     */
    public boolean isEmpty() {
        return map.isEmpty();
    }

    /**
     * Returns <tt>true</tt> if this set contains the specified element.
     * More formally, returns <tt>true</tt> if and only if this set
     * contains an element <tt>e</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
     *
     * @param o element whose presence in this set is to be tested
     * @return <tt>true</tt> if this set contains the specified element
     */
    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    /**
     * 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;
    }

    /**
     * Removes the specified element from this set if it is present.
     * More formally, removes an element <tt>e</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>,
     * if this set contains such an element.  Returns <tt>true</tt> if
     * this set contained the element (or equivalently, if this set
     * changed as a result of the call).  (This set will not contain the
     * element once the call returns.)
     *
     * @param o object to be removed from this set, if present
     * @return <tt>true</tt> if the set contained the specified element
     */
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

    /**
     * Removes all of the elements from this set.
     * The set will be empty after this call returns.
     */
    public void clear() {
        map.clear();
    }

    /**
     * Returns a shallow copy of this <tt>HashSet</tt> instance: the elements
     * themselves are not cloned.
     *
     * @return a shallow copy of this set
     */
    @SuppressWarnings("unchecked")
    public Object clone() {
        try {
            MyHashSet<E> newSet = (MyHashSet<E>) super.clone();
            newSet.map = (HashMap<E, Object>) map.clone();
            return newSet;
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

}

针对以上代码执行测试案例:

        Set myHashSet = new MyHashSet();
        System.out.println(myHashSet.add("zhangsan"));
        System.out.println(myHashSet.add("zhangsan"));
        System.out.println(myHashSet.add("zhangsan"));

        System.out.println(myHashSet.remove("zhangsan"));
        System.out.println(myHashSet.remove("zhangsan"));
        System.out.println(myHashSet.remove("zhangsan"));

修改PRESENT为null之后,输出结果为

true
true
true
true
true
true

结果显然不符合要求,无法区分key在HashSet是否存在,使用PRESENT的理论是,使用一个对象,remove和add,key存在时操作返回true,key不存在时返回false,可以对key是否存在作出区分。

当PRESENT为null时,是无法区分的。使用一些空间可以换取操作的有效信息,操作上可以更加灵活,因此是有价值的,到此就回答了最初的问题。

最后抛出一个问题,MyHashSet没有像HashSet一样实现Set接口,因为AbstractSet已经implements Set接口,从刚才的使用上来看MyHashSet和HashSet并无区别,那为什么HashSet还有再实现Set接口,欢迎大家说出自己的看法。

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值