zoukankan      html  css  js  c++  java
  • 面试题: hashset如何保证值不会被重复的

    个人博客网:https://wushaopei.github.io/    (你想要这里多有)

    众所周知,HashSet 的值是不可能被重复的,在业务上经常被用来做数据去重的操作,那么,其内部究竟是怎么保证元素不重复的呢?

    这里将对HashSet 的源码进行逐步的解析:

    当我们对一个HashSet 的实例添加一个值时,使用到的是它的 add 方法,源码如下:

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

    由以上的add 方法内的实现可知,其维护了一个 HashMap 来实现元素的添加;众所周知,HashMap 作为双列集合,它的键是不能够重复的,这里的 PRESENT 是作为占位符的存在,与值重复判断与否没有意义,不作赘述。

    其实,到了这里,我们已经可以知道 HashSet 的值作为 HashMap 中的 key(键)的,可以确定是不会存在重复值存在的情况发生。

    但是,我们要了解的是为什么不会重复,继续深究,这里继续了解对该值的一个不可重复的原因.

    以下是HashSet 引用HashMap的具体位置。

    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, 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();
    
        /**
         * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
         * default initial capacity (16) and load factor (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.
         *
         * @param c the collection whose elements are to be placed into this set
         * @throws NullPointerException if the specified collection is null
         */
        public HashSet(Collection<? extends E> c) {
            map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
            addAll(c);
        }

    HashSet 其实是在构造器内实例化了一个 HashMap 对象,那么可以得知,HashSet 的值不可重复是依赖于 HashMap 的底层对值不可重复的依赖。

    其实

    以下我们进入到 HashMap put()方法中去:

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

    put  方法的实现可知,该 put 方法对传入的 Map key - value 进行了更深一层的 putVal()的处理。

    但这个方法不是我们现在需要了解的,稍后再对这里进行了解。

    进入 putVal() 方法之前,对传入的 key 进行了hash 运算,获取了一个 hash 值:

        static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }

    获取hash 值的规则是 :当 key == null 时,hash 值为0;若不为null,则说明值有效,会通过 hashCode()

    关于 hashCode() 在这里的作用:

    hashCode在上面扮演的角色为寻域(寻找某个对象在集合中区域位置)。hashCode可以将集合分成若干个区域,每个对象都可以计算出他们的hash码,可以将hash码分组,每个分组对应着某个存储区域,根据一个对象的hash码就可以确定该对象所存储区域,这样就大大减少查询匹配元素的数量,提高了查询效率。

    关于Key 不能够重复,这里可以得出了,相同的值得到的 hash 码大概率上是相同的,所以,key 可以保证不会重复,因为重复的值,一定会被覆盖。具体从后面的源码继续看:

      /**
         * Implements Map.put and related methods
         *
         * @param hash hash for key
         * @param key the key
         * @param value the value to put
         * @param onlyIfAbsent if true, don't change existing value
         * @param evict if false, the table is in creation mode.
         * @return previous value, or null if none
         */
        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }

    注意 以上方法 中的这一句:

       if (e.hash == hash & ((k = e.key) == key || (key != null && key.equals(k))))
                            break;

    由此可知,HashMap 的 集合元素 key-value 添加时,这里调用了对象的hashCode和equals方法进行的判断。

    但要注意,原生的 hashCode 和 equals 更多的是在引用的位置上进行了去重校验,如要对具体的值或对象本身进行去重,还需进行重写操作。

    所以又得出一个结论:若要将对象存放到HashSet中并保证对象不重复,应根据实际情况将对象的hashCode方法和equals方法进行重写

  • 相关阅读:
    微信 token ticket jsapi_ticket access_token 获取 getAccessToken get_jsapi_ticket方法
    PHP 日志 记录 函数 支持 数组 对象 新浪 sae 环境 去掉 空格 换行 格式化 输出 数组转字符串
    原生 原始 PHP连接MySQL 代码 参考mysqli pdo
    PHP 数字金额转换成中文大写金额的函数 数字转中文
    使用PHPMailer发送带附件并支持HTML内容的邮件
    设置输出编码格式 header 重定向 执行时间 set_time_limit 错误 报告 级别 error_reporting
    html5 bootstrap pannel table 协议 公告 声明 文书 模板
    指向指针的指针
    二级指针
    c语言:当指针成为参数后
  • 原文地址:https://www.cnblogs.com/wushaopei/p/12283657.html
Copyright © 2011-2022 走看看