在上一篇学习过HashMap(源码解读—HashMap)之后对hashTable也产生了兴趣,随即便把hashTable的源码看了一下。和hashMap类似,但是也有不同之处。
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
实现接口:Map,Cloneable,Serializable
继承自Dictionary,此抽象类同AbstractMap,
1、四个构造方法:
第一个构造方法: public Hashtable() ;
/** * 用默认容量(11)和加载系数(0.75)构造一个空的hashTable * Constructs a new, empty hashtable with a default initial capacity (11) * and load factor (0.75). */ public Hashtable() { //此处竟然没有定义成常量!!!看来oracle的开发人员也是人!!! this(11, 0.75f); }
第二个构造方法: public Hashtable(int initialCapacity)
/** * 用指定的容量和默认的系数构造一个新的hashTable * Constructs a new, empty hashtable with the specified initial capacity * and default load factor (0.75). * * @param initialCapacity the initial capacity of the hashtable. * @exception IllegalArgumentException if the initial capacity is less * than zero. */ public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); }
第三个构造方法: public Hashtable(int initialCapacity, float loadFactor);
/** * 用指定的容量和加载系数构造一个空的hashTable * 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); //初始化容量不允许为0,最小为1 if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; //数组+单向链表作为hashTable的数据存储容器。初始化数组 table = new Entry[initialCapacity]; //计算扩容阀值,当count(hashTable中的所有entry,不是table数组的size) >= threshold的时候进行扩容操作。 //扩容阀值=容量*加载系数 扩容法制最大不超过(MAX_ARRAY_SIZE + 1) threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); //初始化hashSeed,在进行hashCode计算的时候需要使用 initHashSeedAsNeeded(initialCapacity); }
第四个构造方法:public Hashtable(Map<? extends K, ? extends V> t)
/** * Constructs a new hashtable with the same mappings as the given * Map. The hashtable is created with an initial capacity sufficient to * hold the mappings in the given Map and a default load factor (0.75). * * @param t the map whose mappings are to be placed in this map. * @throws NullPointerException if the specified map is null. * @since 1.2 */ public Hashtable(Map<? extends K, ? extends V> t) { //TODO 创建一个新的hashTable,这里可以肯出hashTable的加载系数一直都是0.75,不允许调用处进行设置 //当t的count>11的时候程序取2*t.size(),为甚么取2倍? 标示:XX01 this(Math.max(2*t.size(), 11), 0.75f); //把数据转存到hashTable中 putAll(t); }
在这里调用 public synchronized void putAll(Map<? extends K, ? extends V> t) ;把所有数据放进hashTable中
/** * 把所有的 映射从指定的map复制到hashTable中 * 如果给定的map中的key值已经存在于hashTable中,则将会覆盖hashTable中key所对应的value(hashTable中key值不允许重复) * Copies all of the mappings from the specified map to this hashtable. * These mappings will replace any mappings that this hashtable had for any * of the keys currently in the specified map. * * @param t mappings to be stored in this map * @throws NullPointerException if the specified map is null * @since 1.2 */ public synchronized void putAll(Map<? extends K, ? extends V> t) { //foreach 循环map数据put到hashTable中 for (Map.Entry<? extends K, ? extends V> e : t.entrySet()) put(e.getKey(), e.getValue()); }
循环调用 public synchronized V put(K key, V value) ;进行entry复制
/** * 把给定的key和value进行映射后放入hashTable, * key和value值都不允许为null(hashMap是允许key和value为null的) * 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> * *通过调用get(K key)方法可以取出value值 * 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) */ //synchronized:看到这个关键字没?这就是hashTable线程安全的原因,加锁了,不允许两个线程同时操作此方法 public synchronized V put(K key, V value) { // Make sure the value is not null 不允许value为null,否则抛出 空指针异常 if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry tab[] = table; //取hashCode int hash = hash(key); //计算table下标(hashTable的数据存储容器为数组+链表(链表是为了解决hash冲突)) //至于为什么这样计算?我也不知道,智商是硬伤啊! int index = (hash & 0x7FFFFFFF) % tab.length; //从table取出entrust(链表头结点,如果有的话),然后循环链表找到key,如果找到则进行value覆盖做操 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; } } //如果hashTable没有该映射则新增 //操作次数++ modCount++; //判断是否需要进行扩容,注意:threshold不是table的长度,而是capacity*loadFactor 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. 创建一个新的节点,把他放入table Entry<K,V> e = tab[index]; tab[index] = new Entry<>(hash, key, value, e); //数据量++ count++; return null; }
2、其他重点方法
keySet和valueSet的迭代器使用
/** * 返回key的迭代器/枚举器?该怎么叫呢? * Returns an enumeration of the keys in this hashtable. * * @return an enumeration of the keys in this hashtable. * @see Enumeration * @see #elements() * @see #keySet() * @see Map */ public synchronized Enumeration<K> keys() { return this.<K>getEnumeration(KEYS); } /** * 返回value的枚举器,通过这个没去器可以连续取出value的值 * Returns an enumeration of the values in this hashtable. * Use the Enumeration methods on the returned object to fetch the elements * sequentially. * * @return an enumeration of the values in this hashtable. * @see java.util.Enumeration * @see #keys() * @see #values() * @see Map */ public synchronized Enumeration<V> elements() { return this.<V>getEnumeration(VALUES); } //根据type(KEY/VALUE)取出对应的枚举器 private <T> Enumeration<T> getEnumeration(int type) { //如果hashTable数据为空则返回空的枚举器 if (count == 0) { return Collections.emptyEnumeration(); } else { return new Enumerator<>(type, false); } } //获取迭代器,没什么可分析的,如果不了解可以找一下迭代器的使用 private <T> Iterator<T> getIterator(int type) { if (count == 0) { return Collections.emptyIterator(); } else { return new Enumerator<>(type, true); } }
根据key取出value: public synchronized V get(Object key);
/** * 取出给定的key所映射的value,如果hashTable中没有此key则返回null * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * *下面又啰嗦了一遍,这里的key相等时根据key.equals(k)来进行判断的,也就是字符串内容相等就OK * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key.equals(k))}, * then this method returns {@code v}; otherwise it returns * {@code null}. (There can be at most one such mapping.) * * @param key the key whose associated value is to be returned * @return the value to which the specified key is mapped, or * {@code null} if this map contains no mapping for the key * @throws NullPointerException if the specified key is null * @see #put(Object, Object) */ public synchronized V get(Object key) { //还是套路:计算index,从table中取出entry节点,再循环链表找key.equals(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) { //判断相等的时候需要key的hashCode和内容同时相同才可 if ((e.hash == hash) && e.key.equals(key)) { return e.value; } } return null; }
扩容操作:当count(entry节点的数量/Key的数量) >= threshold 时进行扩容
/** * 增加容量并且对hashTable进行内部重整,以便于有效的接收容纳更多的entry。 * 当key的数量大于hashTable的阀值时会自动调用这个方法。 * Increases the capacity of and internally reorganizes this * hashtable, in order to accommodate and access its entries more * efficiently. This method is called automatically when the * number of keys in the hashtable exceeds this hashtable's capacity * and load factor. */ protected void rehash() { //套路:先保存老数据 int oldCapacity = table.length; Entry<K,V>[] oldMap = table; // overflow-conscious code //计算出新的容量:(oldCapacity*2)+1 int newCapacity = (oldCapacity << 1) + 1; //如果扩容之后的容量大于容量的最大值则进行判断 if (newCapacity - MAX_ARRAY_SIZE > 0) { //如果容量已经是最大值了则无法集训进行扩容,只能return了。终止操作 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); //TODO 标示:XX03 boolean rehash = initHashSeedAsNeeded(newCapacity); table = newMap; //循环table for (int i = oldCapacity ; i-- > 0 ;) { //循环链表 for (Entry<K,V> old = oldMap[i] ; old != null ; ) { //注意这里,这里传递的知识引用,也就是说在堆空间中并没有复制一个新的entry, //只是把原来的entry的引用复制了一份(不了解的可以查找一下java 的堆栈内存数据存储) 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]; //把老数据放到新的table中 newMap[index] = e; } } }
hashTable继承了序列化和克隆接口,在这里没有对序列化读写方法和克隆方法进行分析,这些都是固定的格式,感兴趣的朋友可以看一些这些接口的使用方法。
在这里还涉及到keySet valueSet 和entrySet,这主要是用来进行迭代使用的,对迭代器不了解的可以找一些迭代器使用的相关资料,这些都是套路,不再分析。
所有的数据操作都离不开增删改查,而这些不同方法很多步骤都是相同的,先进行数据验证,然后进行数据查找,找到之后进行删、改操作。抓住核心其实就没什么难理解的了。
-------------------------------------over--------------------------------------------------