1、Hashtable-源码分析-底层数据结构
1、Hashtable-源码分析-底层数据结构:与HashMap底层数据结构一样、2、Hashtable-源码中属性字段: /** * 键值对/Entry数组,每个Entry本质上是一个单向链表的表头 */ private transient Entry<?, ?>[] table; /** * 当前表中的Entry数量,如果超过了阈值,就会扩容,即调用rehash方法 */ private transient int count; /** * rehash阈值 * * @serial */ private int threshold; /** * 负载因子 * * @serial */ private float loadFactor; /** * 用来实现"fail-fast"机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行 * 迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出 * ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你(你已经出错了)。 */ private transient int modCount = 0;
2、Hashtable-源码分析-构造方法
1、Hashtable-源码分析-构造方法:有4个构造方法2、源代码: //默认构造函数,指定的容量大小是11;加载因子是0.75 public Hashtable() { this(11, 0.75f); } //指定容量大小的构造函数,加载因子是0.75 public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } //指定容量大小和加载因子的构造函数 public Hashtable(int initialCapacity, float loadFactor) { //验证初始容量,初始容量不能小于0 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,获得大小为initialCapacity的table数组 table = new Entry<?,?>[initialCapacity]; //计算阀值 threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); } //构造一个与给定的 Map 具有相同映射关系的新哈希表,容量是原来容量的两倍和11的最大值。 public Hashtable(Map<? extends K, ? extends V> t) { //设置table容器大小,其值==t.size * 2 + 1 Vs 11的最大值 this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }
3、Hashtable-源码分析-Entry类
1、Entry类,与其他Map实现类一样,Hashtable中Entry类实现了Map.Entry<K,V>。2、源代码: /** * Hashtable bucket collision list entry * 哈希表bucket冲突列表项 * Hashtable的Entry节点,它本质上是一个单向链表。 */ 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; } // 进行判断value是否为空,即不允许value为空,其实key也不能为空 public V setValue(V value) { if (value == null) throw new NullPointerException(); V oldValue = this.value; this.value = value; return oldValue; } 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())); } public int hashCode() { // 直接用hash进行异或,与HashMap不同 return hash ^ Objects.hashCode(value); } public String toString() { return key.toString()+"="+value.toString(); } }
4、Hashtable-源码分析-添加元素
1、Hashtable-源码分析-添加元素:.put()方法2、源代码: //添加元素,要求key和value都不能为null public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } /* * 确保key在table[]是不重复的 * 处理过程: * 1、计算key的hash值,确认在table[]中的索引位置 * 2、迭代index索引位置,如果该位置处的链表中存在一个一样的key,则替换其value,返回旧值。 */ Entry<?,?> tab[] = table; //计算key的hash值 int hash = key.hashCode(); //确认该key的索引位置 //&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而 hash & 0x7FFFFFFF 后,只有符号外改变,而后面的位都不变。 int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; //迭代,寻找该key,替换 for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } //执行到这里,说明key没有重复,则进行插入操作 addEntry(hash, key, value, index); //如果是插入全新的Entry的话,就返回空 return null; } /** 进行插入操作 */ 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; //计算key的索引 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); //容器中元素+1 count++; }3、.put()方法,执行过程: 3-1、计算key的hash值,根据hash值获得key在table数组中的索引位置,然后迭代该key处的Entry链表(我们暂且理解为链表),若该链表中存在一个这个的key对象,那么就直接替换其value值即可,否则在将改key-value节点插入该index索引位置处。4、.put()方法,特殊点: 4-1、在put方法中,如果需要向table[]中添加Entry元素,会首先进行容量校验,如果容量已经达到了阀值,HashTable就会进行扩容处理rehash()。
5、Hashtable-源码分析-扩容方法
1、Hashtable-源码分析-扩容方法:.rehash();2、源代码: /** 在put方法中,如果需要向table[]中添加Entry元素,会首先进行容量校验,如果容量已经达到了阀值,HashTable就会进行扩容处理rehash()。 */ @SuppressWarnings("unchecked") protected void rehash() { //旧容量=链表的长度 int oldCapacity = table.length; //获取旧链表,将老数组赋值给oldMap Entry<?,?>[] oldMap = table; // overflow-conscious code //新容量=旧容量 * 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; } //新建一个size = newCapacity 的HashTable Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; //重新计算阀值:在新容量*加载因子和最大值 + 1之间取最小值 threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; //将原来的元素拷贝到新的HashTable中 //双重遍历老数组,第一重遍历是遍历桶上的各个Entry,将老数组的节点移到新数组 for (int i = oldCapacity ; i-- > 0 ;) { //第二重遍历是遍历桶上Entry后面接的链表 for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { //将old赋值给e,e代表遍历到的节点 Entry<K,V> e = old; //old指向他的后继节点,下次遍历就遍历到后继节点了 old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; //把新桶上已存在链表节点作为当前节点的后继节点 e.next = (Entry<K,V>)newMap[index]; //遍历到的节点放到新数组的桶上,[!!!]是链表的头结点 newMap[index] = e; } } }
6、Hashtable-源码分析-获取元素
1、Hashtable-源码分析-获取元素:.get()方法2、源代码: @SuppressWarnings("unchecked") public synchronized V get(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); //桶的索引是用key的hash值和0x7FFFFFFF做与运算,最后对数组长度取余 int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } //如果是插入全新的Entry的话,就返回空 return null; }3、.get()方法-执行过程: 3-1、计算key的hash值,判断在table数组中的索引位置,然后迭代链表,匹配直到找到相对应key的value,若没有找到返回null。
7、Hashtable-源码分析-删除元素
1、Hashtable-源码分析-删除元素:.remove()方法2、源代码: public synchronized V remove(Object key) { Entry<?,?> tab[] = table; //计算key的hash int hash = key.hashCode(); //根据hash计算桶的索引index 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) { //如果key的hash相等,而且key本身也相等,说明就是要删掉这个节点 if ((e.hash == hash) && e.key.equals(key)) { //自增 modCount++; //如果prev不为空,说明要删除的不是头结点 if (prev != null) { //那么将目标节点的头结点指向目标节点的后继节点 prev.next = e.next; } else { //如果删除的就是头结点,那么将原来头结点的后继节点指定为头结点 tab[index] = e.next; } //元素总数 - 1 count--; //取出要删除的节点的value V oldValue = e.value; //将e的value置空(需要吗?) e.value = null; //返回删除节点的value return oldValue; } } return null; }
8、Hashtable-源码分析-遍历元素
1、Hashtable-源码分析-遍历元素:方法有很多种,此处只介绍Enumeration方式2、Enumeration类-源代码: /** * Enumerator的作用是提供了通过elements()遍历Hashtable的接口和通过entrySet()遍历Hashtable的接口。因为,它同时实现了 Enumerator接口和Iterator接口。 */ private class Enumerator<T> implements Enumeration<T>, Iterator<T> { // 指向Hashtable的table Entry<?, ?>[] table = Hashtable.this.table; // Hashtable的总的大小 int index = table.length; Entry<?, ?> entry; Entry<?, ?> lastReturned; int type; /** * Enumerator是 迭代器(Iterator) 还是 枚举类(Enumeration)的标志 * iterator为true,表示它是迭代器;否则,是枚举类。 */ boolean iterator; /** * 在将Enumerator当作迭代器使用时会用到,用来实现fail-fast机制。 */ protected int expectedModCount = modCount; Enumerator(int type, boolean iterator) { this.type = type; this.iterator = iterator; } /** * 从遍历table的数组的末尾向前查找,直到找到不为null的Entry。 */ public boolean hasMoreElements() { Entry<?, ?> e = entry; int i = index; Entry<?, ?>[] t = table; /* Use locals for faster loop iteration */ while (e == null && i > 0) { e = t[--i]; } entry = e; index = i; return e != null; } /** * 获取下一个元素 * 注意:从hasMoreElements() 和nextElement() 可以看出Hashtable的elements()遍历方式 * 首先,从后向前的遍历table数组。table数组的每个节点都是一个单向链表(Entry)。 * 然后,依次向后遍历单向链表Entry。 */ @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的判断是否存在下一个元素 // 实际上,它是调用的hasMoreElements() public boolean hasNext() { return hasMoreElements(); } // 迭代器获取下一个元素 // 实际上,它是调用的nextElement() public T next() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); return nextElement(); } // 迭代器的remove()接口。 // 首先,它在table数组中找出要删除元素所在的Entry, // 然后,删除单向链表Entry中的元素。 public void remove() { if (!iterator) throw new UnsupportedOperationException(); if (lastReturned == null) throw new IllegalStateException("Hashtable Enumerator"); if (modCount != expectedModCount) throw new ConcurrentModificationException(); synchronized (Hashtable.this) { Entry<?, ?>[] tab = Hashtable.this.table; int index = (lastReturned.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 == lastReturned) { modCount++; expectedModCount++; //删除上一个元素 if (prev == null) tab[index] = e.next; else prev.next = e.next; count--; lastReturned = null; return; } } throw new ConcurrentModificationException(); } } }3、Demo //通过Enumeration来遍历Hashtable Enumeration<String> enu = table.keys(); while(enu.hasMoreElements()) { System.out.println("Enumeration:"+table.keys()+" "+enu.nextElement()); }