HashSet理解(二)怎么做到值不重复

HashSet理解(一)java集合
HashSet理解(二)怎么做到值不重复
HashSet理解(三)add方法(jdk1.7及以前)是如何插值的
HashSet理解(四)为什么jdk1.7中的头插法会形成环和死循环?

HashSet的实现,是一个value为PRESENT的HashMap。

为什么value设置为PRESENT,设为null不是更加节省空间吗?

因为HashSet的add方法,要返回一个boolean值,为true,表示添加了新的元素,false表示添加了一个重复的值。add方法内部调用了HashMap的put方法:

   public boolean add(E e) {
     
     
      return map.put(e, PRESENT)==null;
   }
   
   //HashMap
  public V put(K key, V value) {
     
     
       return putVal(hash(key), key, value, false, true);
  }

HashMap允许value为null,且put方法会返回参数key对应的,上次插入的value,如果这个key之前没有插入值,则返回null。如下:

public class MapPutTest {
     
     
  public static void main(String[] args) {
     
     
       HashMap<String, String> map = new HashMap<>();
       System.out.println(map.put("a", "null"));
       System.out.println(map.put("a", "A"));
       System.out.println(map.put("a", "Apple"));
       System.out.println(map.put("b", "B"));
       System.out.println(map.put("b", "Blue"));
   }
}

可以看到,返回null有两种情况,一是参数key还没有对应的value,二是参数key有对应的value,但是值为null。
  如果HashSet的value都设成null,那么第一次add(“a”),返回为true, 合理。因为添加了新元素,此时a对应的value为null。第二次add(“a”),返回的还是true。就不合理了,应该返回false,因为a是重复元素。
 如果HashSet的value设置成PRESENT,就不会有这个问题,第一次add(“a”),map返回null, null==null得到true。第二次add(“a”),map返回PRESENT,PRESENT==null得到false,合理。

HashSet怎么做到值不重复的?

在jdk1.6中,HashMap的put方法,根据参数key的hashCode找到对应的数组下标,取出该下标对应的链表,遍历这个链表中所有已插入节点的key值,和参数key做比较,如果相同,则修改对应的value值。如果所有key都和参数key不同,就将新的Entry插入到链表中。 详细的代码分析,看下面的问题。

HashMap判断两个key值相同的条件是什么?

两个key对象equals()方法比较结果为true,并且两个对象的hashCode值相同。
看jdk1.6的代码,解释都在注释里面,仔细看注释。

public V put(K key, V value) {
     
     
        //如果 key 为 null,最终会将这个 entry 放到 table[0] 中
     if (key == null)
           return putForNullKey(value);
     // 1. 求 key 的 hash 值
      int hash = hash(key.hashCode());
     // 2. 找到对应的数组下标
       int i = indexFor(hash, table.length);
      // 3. table[]是一个entry数组,遍历一下对应下标处的链表,看是否有重复的 key 已经存在,
     //    如果有,直接覆盖,put 方法返回旧值就结束了
       for (Entry<K,V> e = table[i]; e != null; e = e.next) {
     
     
           Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
     
     
               V oldValue = e.value;
               e.value = value;
               e.recordAccess(this);
               return oldValue;
           }
       }

       modCount++;
     // 4. 不存在重复的 key,将此 entry 添加到链表中
       addEntry(hash, key, value, i);
       return null;
   }

equals()相等,hashCode()是否也要相等?

 下面是Object类中,关于hashCode()方法的注释:

	/**
     * Returns a hash code value for the object. This method is
     * supported for the benefit of hash tables such as those provided by
     * {@link java.util.HashMap}.
     * <p>
     * The general contract of {@code hashCode} is:
     * <ul>
     * <li>Whenever it is invoked on the same object more than once during
     *     an execution of a Java application, the {@code hashCode} method
     *     must consistently return the same integer, provided no information
     *     used in {@code equals} comparisons on the object is modified.
     *     This integer need not remain consistent from one execution of an
     *     application to another execution of the same application.
     * <li>If two objects are equal according to the {@code equals(Object)}
     *     method, then calling the {@code hashCode} method on each of
     *     the two objects must produce the same integer result.
     * <li>It is <em>not</em> required that if two objects are unequal
     *     according to the {@link java.lang.Object#equals(java.lang.Object)}
     *     method, then calling the {@code hashCode} method on each of the
     *     two objects must produce distinct integer results.  However, the
     *     programmer should be aware that producing distinct integer results
     *     for unequal objects may improve the performance of hash tables.
     * </ul>
     * <p>
     * As much as is reasonably practical, the hashCode method defined by
     * class {@code Object} does return distinct integers for distinct
     * objects. (This is typically implemented by converting the internal
     * address of the object into an integer, but this implementation
     * technique is not required by the
     * Java&trade; programming language.)
     *
     * @return  a hash code value for this object.
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.lang.System#identityHashCode
     */
    public native int hashCode();

翻译过来就是:

返回对象的哈希值。这个方法是为了支持哈希表,例如HashMap中提供的哈希表。
hashCode方法的一般规则是:

  • 在 Java 应用程序执行期间,只要在同一个对象上多次调用它,hashCode 方法必须始终返回相同的整数,前提是在对象的 equals 比较中使用的信息没有被修改。该整数不需要从应用程序的一次执行到同一应用程序的另一次执行保持一致。(后面这句话的意思是,同一个应用程序启动后,关闭,再启动,这两次执行的hashCode可以不一样。)
  • 如果根据 equals(Object) 方法两个对象相等,则这两个对象调用 hashCode 方法必须产生相同的整数结果。
  • 如果根据 equals(java.lang.Object) 方法两个对象不相等,则两个对象调用 hashCode 方法时,不必产生不同的整数结果。但是,程序员应该意识到为不相等的对象生成不同的整数结果可能会提高哈希表的性能。

就合理实用而言,类 Object 定义的 hashCode 方法确实为不同的对象返回不同的整数。(这通常是通过将对象的内部地址转换为整数来实现的,但是 Java编程语言不需要这种实现技术。)

这就是许多博客上说的重写hashCode的三大原则的由来,简单的讲:

  • 同一个对象多次调用hashCode方法,equals中比较的属性没变的前提下,hashCode应该返回相同的值。
  • 如果两个对象的equals比较为true,那么这两个对象的hashCode值必须相等。
  • 如果两个对象的equals比较为false, 两个对象的hashCode值不一定要不同,但最好不同,有利于提升哈希表的性能。

这是hashCode的一般规则,实际编写代码的时候,equals和hashCode都可以重写自定义,所以equals方法为true还是false,与hashCode返回值是否相等,没有必然联系。

Guess you like

Origin blog.csdn.net/zhangjin1120/article/details/121063452