Hashtable
核心成员变量
private transient Entry<?,?>[] table;
,存放键值对
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
@SuppressWarnings("unchecked")
protected Object clone() {
return new Entry<>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
// Map.Entry Ops
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
...
}
public boolean equals(Object o) {
...
}
public int hashCode() {
return hash ^ Objects.hashCode(value);
}
public String toString() {
return key.toString()+"="+value.toString();
}
}
构造器
public Hashtable(int initialCapacity, float loadFactor)
,主要构造器Hashtable(int initialCapacity)
,实质是调用1,默认loadFactor=0.75
Hashtable()
,实质是调用1,默认initialCapacity=11
、loadFactor=0.75
Hashtable(Map<? extends K, ? extends V> t)
,实质是调用1,容量初始大小在原大小的两倍和11之间取大者,默认loadFactor=0.75
。
成员方法
put(K key, V value)
,借助addEntry
方法添加新结点
细节:hash & 0x7FFFFFFF
,保证hash值为正。不用abs函数的原因是,abs函数无法处理最小负数-2^31
。- 采用
%
实现取余。这里不同于HashMap
,没有tableSizeFor
保证容器大小始终为2的幂。因此,只能采用%
符合。 value == null
、key.hashCode();
导致Hashtable
键与值均不能为null
,否则均会抛出空指针异常
那么问题来了
Hashtable
为什么不采用HashMap
的策略,始终保持容器为2的幂?Hashtable
为什么没有树化机制?- 为什么
Hashtable
继承的是Dictionary
类以实现键值对,而HashMap
通过继承AbstractMap
类以实现键值对?
这两个问题答案都是一样的。
Dictionary
类是过时类,Hashtable
是1.0版本实现键值对的类。现在因为线程安全的特点而被继续使用。
另外,Hashtable
没有采用驼峰命名,也是因为驼峰命名是在1.2版本约定的。
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
addEntry(int hash, K key, V value, int index)
,借助rehash
实现扩容
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
扩容机制rehash
- 每次扩容为原容量的两倍+1,
(oldCapacity << 1) + 1
- 若新容量超过最大值则不再扩容
注意,没有树化机制
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
//重新哈希散列存储旧表
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
entrySet()、EntrySet、Enumerator
Enumerator
等同于HashMap
类的EntryIterator
。功能逻辑上与HashMap
是一致的。
不同细节:
- 使用
Enumerator
统一实现HashMap
类中xxxIterator
的功能 - 构造
Enumerator
对象时传入type
,如KEYS
、VALUES
、ENTRIES
用于控制迭代时返回键、值还是键值对。
public Set<Map.Entry<K,V>> entrySet() {
if (entrySet==null)
entrySet = Collections.synchronizedSet(new EntrySet(), this);
return entrySet;
}
private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return getIterator(ENTRIES);
}
public boolean add(Map.Entry<K,V> o) {
return super.add(o);
}
public boolean contains(Object o) {
...
}
public boolean remove(Object o) {
...
}
public int size() {
return count;
}
public void clear() {
Hashtable.this.clear();
}
}
private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
Entry<?,?>[] table = Hashtable.this.table;
int index = table.length;
Entry<?,?> entry;
Entry<?,?> lastReturned;
int type;
/**
* Indicates whether this Enumerator is serving as an Iterator
* or an Enumeration. (true -> Iterator).
*/
boolean iterator;
/**
* The modCount value that the iterator believes that the backing
* Hashtable should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
protected int expectedModCount = modCount;
Enumerator(int type, boolean iterator) {
this.type = type;
this.iterator = iterator;
}
public boolean hasMoreElements() {
...
}
@SuppressWarnings("unchecked")
public T nextElement() {
Entry<?,?> et = entry;
int i = index;
Entry<?,?>[] t = table;
/* Use locals for faster loop iteration */
while (et == null && i > 0) {
et = t[--i];
}
entry = et;
index = i;
if (et != null) {
Entry<?,?> e = lastReturned = entry;
entry = e.next;
return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
}
throw new NoSuchElementException("Hashtable Enumerator");
}
// Iterator methods
public boolean hasNext() {
return hasMoreElements();
}
public T next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return nextElement();
}
public void remove() {
...
}
}
Properties
继承了Hashtable
类,主要是为IO流
中读取、修改配置文件服务,配置文件常为.properties
文件。这种设计体现了高内聚、低耦合的思想。
方法和成员变量较为简单,感兴趣的朋友可以看这位博主的博文。
https://blog.youkuaiyun.com/weixin_53972936/article/details/123899368
需要注意一点,Properties
中存的键值对都必须是String
类型。为此在Properties
中通过独有的setProperty
、getProperty
方法进行了控制。但如果利用父类的方法存入了非String
类型的键值对,这种不安全的做法将导致调用不了store
和save
方法。