1.Entry结构
HashMap的节点结构叫Node(JDK1.8,JDK1.8以前也叫Entry),Hashtable为Entry,都包含了四个相同的字段。
private static class Entry<K,V> implements Map.Entry<K,V> { //hash值 final int hash; //键值 final K key; //value V value; //下一个Entry的引用 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) { if (value == null) throw new NullPointerException(); V oldValue = this.value; this.value = value; return oldValue; } /** * equal方法 * @param o * @return */ public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; return (key==null ? e.getKey()==null : key.equals(e.getKey())) && (value==null ? e.getValue()==null : value.equals(e.getValue())); } /** * hashcode方法 * @return */ public int hashCode() { return hash ^ Objects.hashCode(value); } public String toString() { return key.toString()+"="+value.toString(); } }
2.字段和常量
Hashtable同样使用数组作为哈希表,有加载因子和阈值
/** * 哈希表,数组实现,存储数据 */ private transient Entry<?,?>[] table; /** * 哈希表中实际存储对象的个数 */ private transient int count; /** * 阈值,容量*加载因子 */ private int threshold; /** * 加载因子 */ private float loadFactor; /** * 修改次数 */ private transient int modCount = 0; /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = 1421746759512286392L; /** * 最大数组大小 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
3.构造函数
Hashtable默认初始容量是11,加载因子0.75,容量不用是2的整数次幂
/** * 带有初始容量和加载因子的构造函数 */ 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,设置初始容量为1 if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; //创建哈希表 table = new Entry<?,?>[initialCapacity]; //设置阈值 threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); } /** * 带有初始容量的构造函数,加载因子使用默认的值 */ public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } /** * 无参构造函数,创建一个初始容量为11,加载因子为0.75的哈希表 */ public Hashtable() { this(11, 0.75f); } /** * 根据指定的Map创建哈希表 */ public Hashtable(Map<? extends K, ? extends V> t) { //map中对象个数的2倍与11比较,选取大的那个数作为初始容量 this(Math.max(2*t.size(), 11), 0.75f); //将map中的元素加入到哈希表 putAll(t); }
4.获取方法
(1)计算key的hash值时直接使用的key的hashcode
(2)计算索引时先将hash与0x7FFFFFFF做&运算,再对哈希表长度取余数。hash&0x7FFFFFFF是为了将负的hash转为正值。
/** * 根据key获取对象 */ @SuppressWarnings("unchecked") public synchronized V get(Object key) { Entry<?,?> tab[] = table; //计算hash int hash = key.hashCode(); //计算索引 int index = (hash & 0x7FFFFFFF) % tab.length; //定位到index索引处,遍历链表寻找对象 for (Entry<?,?> e = tab[index]; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; }
5.添加方法
(1)hashtable添加对象时value不能为null,如果为null抛出异常。
(2)添加对象时首先会判断是否超过阈值,如果超过调用rehash方法对哈希表扩容。扩容时新容量为原来容量的2倍+1,并且会重写计算每一个节点在新哈希表中的索引,产生哈希冲突时,使用头插法插入链表,因此链表内容会倒置。
(3)在addEntry方法中可以看出,添加新节点时产生哈希冲突同样使用头插法插入链表。
(4)如果已经存在键值为key的对象,新的value会覆盖旧的value。
/** * 调整哈希表大小 */ @SuppressWarnings("unchecked") protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // 新的容量为旧容量的2倍+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<?,?>[] 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; //使用头插法将节点插入到index处 e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } } /** * 添加Entry * @param hash * @param key * @param value * @param index */ private void addEntry(int hash, K key, V value, int index) { //修改次数 modCount++; Entry<?,?> tab[] = table; //如果哈希表中容量超过阈值 if (count >= threshold) { // 扩容,创建新的哈希表 rehash(); tab = table; //根据key重新计算hash和索引 hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index];//获取index处的元素 //创建新节点,next指向原来index处的对象e,然后将新节点放到index位置,可以看出使用的是头插法插入链表 tab[index] = new Entry<>(hash, key, value, e); count++; } /** * 添加 */ public synchronized V put(K key, V value) { // 如果值为null抛出异常 if (value == null) { throw new NullPointerException(); } 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; } /** * 将map中的元素添加到哈希表 */ public synchronized void putAll(Map<? extends K, ? extends V> t) { //遍历map for (Map.Entry<? extends K, ? extends V> e : t.entrySet()) //添加节点 put(e.getKey(), e.getValue()); }
6.删除
/** * 根据指定的key删除对象 */ public synchronized V remove(Object key) { Entry<?,?> tab[] = table; //计算hash和索引 int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; //遍历链表 for(Entry<K,V> prev = null; e != null ; prev = e, e = e.next) { //找到需要删除的对象 if ((e.hash == hash) && e.key.equals(key)) { modCount++; //如果要删除的节点不是头结点,将要删除节点的前一个节点的next指向要删除节点的下一个节点 if (prev != null) { prev.next = e.next; } else {//否则将要删除节点的下一个节点作为头结点 tab[index] = e.next; } //更改大小 count--; V oldValue = e.value; //删除节点 e.value = null; //返回要删除节点的值 return oldValue; } } return null; } /** * 清空 */ public synchronized void clear() { Entry<?,?> tab[] = table; modCount++; //遍历哈希表,将数组清空 for (int index = tab.length; --index >= 0; ) tab[index] = null; count = 0; }
Hashtable与HashMap不同点:
1. Hashtable默认容量为11,并且容量不需要是2的整数次幂,HashMap默认容量为16,容量需要是2的整数次幂。
2. Hashtable的key和value不允许为null,但是从源码中未看到对key做null判断,如果value为空运行时会抛出异常。
HashMap的key和value允许为null,key为null时hash值为0。
3. Hashtable扩容时,新容量为原来的2倍+1,HashMap扩容时新容量为原来的2倍。
4. Hashtable计算key的hash值时直接使用key的hashcode,计算索引时hash&0x7FFFFFFF后对length-1取余数。
HashMap计算key的hash值时将hashcode的低16位与高16位做异或,计算索引时hash值对length-1做&运算。(JDK1.8)
5. Hashtable使用链表解决哈希冲突,冲突时使用头插法插入链表。
HashtMap使用链表+红黑树的结构解决冲突,用尾插法插入链表。(JDK1.8)
6. Hashtable扩容时会重新计算每个节点的索引,而且使用头插法链表会倒置。
HashMap扩容时进行了优化,每个节点的索引要么不变要么是原来索引+哈希表容量,并且链表不会倒置。(JDK1.8)
7. Hashtable添加、删除等方法使用了synchronized,是线程安全的,HashMap是线程不安全的。
相同点:
1. Hashtable与HashMap都使用数组实现,Hashtable的存储结构Entry与HashMap的存储结构Node虽然类名不同,但是字段相同,都包含了hash、key、value和next。
2.Hashtable和HashMap都实现类Serializable接口,支持序列化,实现了Cloneable,可以被克隆。