一直写一些工作日常笔记,最近想静下来看下一些常见的知识原理,在很多blog上都有看到过,不过看到的东西是别人的,只有自己去看了源码,写了demo,理解了才是自己的东西。
hashTable(jdk1.7)
定义:
继承与Dictionary,实现了Map的一些方法,标记了这个对象Clone,以及序列化Serializable
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
构造器及初始化:
不传任何参数的构造器,系统默认容量为11,hash因子0.75
new Hashtable();//this(11, 0.75f);
传入容量大小的initialCapacity,
Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
传入容量(initialCapacity)和hash因子(loadFactor)
new Hashtable(int initialCapacity, float loadFactor)
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);
//容量为0,设置一个容量为,保证传入不是IsEmpty
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
//创建一个Entry数组,Entry是一个单链表结构
table = new Entry[initialCapacity];
//设置一个阀值threshold=容量*hash因子,超过这个阀值,需要对数组扩容
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
initHashSeedAsNeeded(initialCapacity);
}
构造其中主要是初始化容量值,hash因子,threshold,创建一个单链表对象数组。还看到一个initHashSeedAsNeeded方法,这个方法主要初始化hashSeed参数,其中hashSeed用于计算key的hash值,它与key的hashCode进行按位异或运算。这个hashSeed是一个与实例相关的随机值,主要用于解决hash冲突。
重要方法:
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;
//使用hashSend与运算key.hashCode
int hash = hash(key);
//hash & 0x7FFFFFFF防止hash为负值吧
//这里使用除法散列
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
//遇到hash值相同的插入,使用单链表连接起来,解决hash冲突
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
//modCount没修改一次计数加1,与添加数量无关,用于遍历时候检查操作合法性
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
//重hash 数组中元素超过阀值,需要再次计算容量,扩容数组
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);
count++;
return null;
}
put方法比较简单,就是先对key值hash然后与hashSend这个随机值与运行,这样做的好处是减少hash重复。然后使用除法散列法,把对象放入到entry数组,如果遇到hash值重复,使用单链表把相同hash值的元素连接起来。但数组达到阀值(threshold),对创建一个新数组进行扩容。
接下来看下rehash
protected void rehash() {
int oldCapacity = table.length;
Entry<K,V>[] oldMap = table;
// overflow-conscious code
//计算新数组容量,左移1位,new=2*old+1
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<K,V>[] newMap = new Entry[newCapacity];
modCount++;
//重新生成阀值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
boolean rehash = initHashSeedAsNeeded(newCapacity);
table = newMap;
//将原来的元素拷贝到新的HashTable中
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
接下看看putAll,这个方法没什么说的,遍历传入集合,最终还是调用put方法
public synchronized void putAll(Map<? extends K, ? extends V> t) {
for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
put(e.getKey(), e.getValue());
}
get方法 先hash转入key,然后找到对应的index,在entry数组中找到对应的entry对象,再通过hash值和key比较找到想要的对象值返回
public synchronized V get(Object key) {
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) {
//hash重复,判断key是否同一个对象
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
看完获取,添加元素后,再来看看删除
同样先hash转入key,然后找到对应的index,在entry数组中找到对应的entry对象,再通过hash值和key比较找到想要的对象,如果当前entry没有prev,直接把该对象的next指向tab[index],断开该对象的链表;
如果该对象有prev,和next就把该对象的prev.next指向该对象的next
public synchronized V remove(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
clear没什么可说的 把所有的元素置null
public synchronized void clear() {
Entry tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}
迭代器Enumeration:
hashTable使用的是Enumeration迭代器接口
Enumeration接口是JDK1.0时推出的,最早使用Vector时就是使用Enumeration接口进行输出。虽然Enumeration是一个旧的类,但是在JDK1.5之后为Enumeration类进行了扩充,增加了泛型的操作应用。
Enumeration接口常用的方法有hasMoreElements()(判断是否有下一个值)和 nextElement()(取出当前元素),这些方法的功能跟Iterator类似,只是Iterator中存在删除数据的方法,而此接口不存在删除操作。
HashTable与HashMap的区别
第一:我们从他们的定义就可以看出他们的不同,HashTable基于Dictionary类,而HashMap是基于AbstractMap。
(Dictionary是什么?它是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作。)
第二:HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。
第三:迭代器不同,hashTable使用的比较古老的Enumeration,而hashMap使用的是Interator
第四:Hashtable的方法是同步的,而HashMap的方法不是。所以有人一般都建议如果是涉及到多线程同步时采用HashTable,没有涉及就采用HashMap,