Hashtable源码分析(基于JDK1.8)

Hashtable 简介
Hashtable 存储的内容是键值对(key-value)映射,其底层实现是一个Entry数组+链表。
Hashtable是线程安全的它的key、value都不可以为null。此外,Hashtable中的映射不是有序的。且Hashtable大部分方法是用synchronized修饰,证明Hashtable是线程安全的。

Hashtable的继承结构
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable{ //do something}
Hashtable继承于Dictionary类(Dictionary类中声明了操作键值对的方法),实现了Map接口(定义键值对接口);

Hashtable的几个重要成员变量
1.private transient Entry<K,V>[] table; 键值对/Entry数组,每个Entry本质上是一个 单向链表
2.private transient int count; Entry的总数
3.private int threshold:rehash阈值,当count超过该阈值会rehash(重排序)
4.private float loadFactor:负载因子
5.private transient int modCount = 0; //用来帮助实现fail-fast机制,叫做结构修改次数 数组修改一次就加一(改变Entry的结构或修改器内部结构)
6.private transient volatile Set<Map.Entry<K,V>> entrySet:键值对集合,不可重复;
7.private transient volatile Set keySet:key的集合,不可重复;
8.private transient volatile Collection values:value集合,可重复;

注意1:

我们必须要知道Hashtable底层是采用数组+链表的结构来实现的

注意2:

加载因子loadFactor是Hashtable扩容前可以达到多满的一个尺度。这个参数是可以设置的,默认是0.75。Hashtable的初始化capacity为11。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 get 和 put 操作,都反映了这一点)。

Hashtable几个重要的方法分析

主要方法:Hashtable()、contains()、get()、rehash()、addEntry()、put(K,V)、remove(Object),像size()、keys()、values()、isEmpty()、elements()比较简单就不一一介绍了。
1.Hashtable()方法:

    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);
    if (initialCapacity==0)
    initialCapacity = 1;
    this.loadFactor = loadFactor;
    table = new Entry[initialCapacity];
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);   //threshold的计算方式,MAX_ARRAY_SIZE防止经过n次扩容后,数组大小超出整数的最大值,所以这里设定一个上限的阈值,   MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    initHashSeedAsNeeded(initialCapacity);
}

对初始容量,负载因子,阈值等判断和计算,
2.contains()方法:

    public synchronized boolean contains(Object value) {
    if (value == null) {  //这里,如果value为空,会报NullPointerException
        throw new NullPointerException();
    }
    Entry tab[] = table;
    for (int i = tab.length ; i-- > 0 ;) {  //从数组的最后往前遍历
        for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
            if (e.value.equals(value)) {
                return true;        //如果找到value,返回true
            }
        }
    }
    return false;				//否则,返回false
}

3.containsKey()方法:

    public synchronized boolean containsKey(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) {
        if ((e.hash == hash) && e.key.equals(key)) {
            return true;
        }
    }
    return false;
}
 计算index, % tab.length防止数组越界, index表示**key对应entry所在链表表头;**除数取余法进行散列分布

4.get()方法:

    public synchronized V get(Object key) {
    Entry tab[] = table;
    int hash = hash(key);
    int index = (hash & 0x7FFFFFFF) % tab.length; //通过key的hash值和table的length,经过运算得到散列表中的索引
    for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {  //遍历tab[index]对应的链表
        if ((e.hash == hash) && e.key.equals(key)) { //如果entry的key和hash值与期望值一致,则返回value
            return e.value;
        }
    }
    return null; //否则,返回null
}

5.rehash()方法:

   protected void rehash() {
    int oldCapacity = table.length;
    Entry<K,V>[] oldMap = table; //保存旧的容量和Entry数组
     // overflow-conscious code
    int newCapacity = (oldCapacity << 1) + 1; 计算新容量 = 2 * 旧容量 + 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++;  //此时,散列表内的元素发生变化,modCount指针加1
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); //重新计算新的threshold
    boolean rehash = initHashSeedAsNeeded(newCapacity);

    table = newMap;

    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; // 重新计算每个Entry链表的表头索引(rehash)
            e.next = newMap[index];   //开辟新节点
            newMap[index] = e;
        }
    }
}

当Hashtable中键值对总数超过阈值(容量*装载因子)后,内部自动调用rehash()增加容量,重新计算每个键值对的hashCode;
int newCapacity = (oldCapacity << 1) + 1计算新容量 = 2 * 旧容量 + 1;并且根据新容量更新阈值;
6.put()方法:

    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 = hash(key);
    int index = (hash & 0x7FFFFFFF) % tab.length;
    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;
        }
    }
    addEntry(hash, key, value, index);  //加入新的键值对
    return null;

设置键值对,key和value都不可为null,设置顺序:

1.如果Hashtable含有key,设置(key, oldValue) -> (key, newValue);

2.如果Hashtable不含有key, 调用addEntry(...)添加新的键值对;

6.addEntry()方法:
private void addEntry(int hash, K key, V value, int index)

			//如果count大于阈值

           if (count >= threshold) {

               //进行重排列,rehash操作,调整整个table   //如果不大于阈值,则直接插入

这个方法需要注意链接新节点的时候,新的结点是链表表头

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值