HashSet在Java中是如何工作的[有例子说明]

112 阅读3分钟

没有多少Java程序员知道HashSet 在Java内部是用HashMap 实现的,所以如果你知道HashMap在Java内部是如何工作的,更有可能你能搞清楚HashSet在Java中是如何工作的。但是,现在一个好奇的Java开发者可以质疑,为什么HashSet 会使用HashMap,因为你需要一个键值对来使用Map,而在HashSet 中我们只存储一个对象。好问题,不是吗?如果你还记得早期类的一些功能,那么你就会知道HashMap允许重复的值,在Java中实现HashSet时就利用了这个属性。

由于HashSet实现了Set接口,它需要保证唯一性,而这是通过将元素作为键来存储的,其值总是相同的。通过查看JDK源代码中的HashSet.java,事情就变得很清楚了。

你所需要看的是,元素是如何存储在HashSet中的,以及如何从HashSet中检索出来的。由于HashSet没有提供任何直接检索对象的方法,例如从HashMap 中获取(Key key)或从List中获取(int index),从HashSet 中获取对象的唯一方法是通过迭代器。关于Java中对HashSet进行迭代的代码示例,请看这里

当你在Java中创建一个HashSet的对象时,它在内部会创建一个备份Map的实例,默认初始容量为16,默认负载系数为0.75,如下所示:

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

现在让我们看看Java中java.util.HashSet的add()和iterate()方法的代码,以了解HashSet在Java中的内部工作

在Java中,对象是如何存储在HashSet中的?

如下所示,对add(Object)的调用是对put(Key, Value)的内部委托,其中Key 是你传递的对象,value是另一个对象,称为PRESENT,它是java.util.HashSet中的一个常数,如下所示。

private transient HashMap<E,Object> map; 

// 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 是一个常数,对于所有的键,我们在备份HashMap中都有相同的值,称为map。

How HashSet Internally Works in Java [Explained]

如何从HashSet中检索对象?

现在让我们看看在Java中获取遍历HashSet的迭代器的代码。java.util.HashSet类中的iterator()方法返回map.keySet().iterator() 方法所返回的备份Map的迭代器 。

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

如何在Java中使用HashSet - 示例

在Java中使用HashSet 是非常简单的,不要认为它是Map ,而是更像Collection,即 使用add()方法添加元素,检查其返回值,看该对象是否已经存在于HashSet 中。同样,在Java中使用一个迭代器来检索HashSet 中的元素。

你也可以使用contains()方法来检查一个对象是否已经存在于HashSet 中。这个方法使用equals()方法来比较对象的匹配。你也可以使用remove()方法来从HashSet中移除对象。由于HashSet 的元素被用作备份HashMap的键,它们必须实现equals()和hashCode()方法。

不可变性不是一个要求,但是如果它是不可变的,那么你可以认为对象在集合中停留期间不会被改变。下面的例子展示了HashSet 在Java中的基本用法,对于更高级的例子,你可以查看这个 教程。

import java.util.HashSet; 
import java.util.Iterator; 
/** * Java Program to demonstrate How HashSet works internally in Java. * @author http://java67.com */ 

public class HashSetDemo{ 
  public static void main(String args[]) { 
    HashSet<String> supportedCurrencies = new HashSet<String>(); 
   // adding object into HashSet, this will be translated to put() calls 
   supportedCurrencies.add("USD"); 
   supportedCurrencies.add("EUR"); 
   supportedCurrencies.add("JPY"); 
   supportedCurrencies.add("GBP"); 
   supportedCurrencies.add("INR"); 
   supportedCurrencies.add("CAD"); 
   // retrieving object from HashSet Iterator<String> itr =
   supportedCurrencies.iterator(); 
   while(itr.hasNext()){ 
     System.out.println(itr.next()); 
    } 
  } 
}

输出

JPY
EUR
INR
USD
CAD
GBP

以上就是关于HashSet如何在Java中实现以及HashSet如何在内部工作的全部内容。正如我所说,如果你知道HashMap在Java中是如何工作的,你就可以解释HashSet的工作原理了,你知道它对所有键都使用相同的值。记住,对于你要存储在HashSet中的任何对象都要覆盖equals()和hashCode(),因为你的对象在备份Map中被用作键,它必须覆盖这些方法。如果可能的话,让你的对象成为Immutable 或有效的immutable。