HashSet
首先看一下HashSet的构造函数:
public HashSet() {
map = new HashMap<>();
}
从这个构造函数可以看出,HashSet的实现其实就是利用HashMap,默认情况下采用的是 initial capacity为16,load factor 为 0.75。
再看一下HashSet的add函数:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
实际上调用的也是HashMap的put函数。只不是放进HashMap里的key值是我们要放进HaseSet里的值,而放进HashMap里的value,就一律都是PRESENT。所以我们使用HashSet,存入HashSet里的元素,其实就是HashMap里的key值。从某种程度上说,HashSet其实就是只取key值的HashMap。
由于HashMap的存储都是通过key值计算hash值来确定存入的坐标,再通过equals方法比较该坐标上已经存在的key,来判断是新存入key-value还是替换value,所以HashMap里的key是唯一不重复的,也就是说HashSet存储的元素也是唯一不重复的。从上面的add方法来看,实际调用了HashMap的put方法。而put方法是,如果存入了key-value的话,返回null,新value替换了旧value的话返回旧value。所以当我们把重复的元素放进HashSet时,map.put(e, PRESENT)就不为null,而add方法得到的返回值就是false。
Hashtable
也先看一下Hashtable的构造方法:
/**
* Constructs a new, empty hashtable with a default initial capacity (11)
* and load factor (0.75).
*/
public Hashtable() {
this(11, 0.75f);
}
/**
* Constructs a new, empty hashtable with the specified initial
* capacity and the specified load factor.
*
* @param initialCapacity the initial capacity of the hashtable.
* @param loadFactor the load factor of the hashtable.
* @exception IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive.
*/
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
initHashSeedAsNeeded(initialCapacity);
}
在里面可以看到,Hashtable像HashMap一样,都是声明了一个Entry数组,但Hashtable默认的初始容量大小是11,负载因子都是0.75。再看一下Hashtable的put方法:
/**
* Maps the specified <code>key</code> to the specified
* <code>value</code> in this hashtable. Neither the key nor the
* value can be <code>null</code>. <p>
*
* The value can be retrieved by calling the <code>get</code> method
* with a key that is equal to the original key.
*
* @param key the hashtable key
* @param value the value
* @return the previous value of the specified key in this hashtable,
* or <code>null</code> if it did not have one
* @exception NullPointerException if the key or value is
* <code>null</code>
* @see Object#equals(Object)
* @see #get(Object)
*/
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException(); // 如果value为null,则抛出空指针异常
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old; // 跟HashMap一样,计算hash值,并且通过equals比较,返回true的就替换
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e); // 把新的Entry存储到数组里
count++;
return null;
}
由此可见,Hashtable与HashMap的一个不同点在于,Hashtable会判断value是否为null,而且Hashtable不像HashMap,会把为null的key的hash值用0来代替。所以Hashtable与HashMap的一大不同点是,Hashtable不允许为null的key和value,而HashMap可以。
另外,Hashtable的put方法(基本上所有的方法),都会有synchronized修饰,代表Hashtable是同步的,而HashMap是非同步的。所以如果是涉及到多线程同步时采用 HashTable,不过Collections 类中有一个静态方法:synchronizedMap(),通过该方法也可以得到一个线程安全的 Map 对象。