Hashtable源码解析

一 简介

1、概念

  和HashMap一样,Hashtable也是一个散列表,它存储的内容是键值对。HashTable与ArrayList一样,是非泛型的,value存进去是object,存取会发生装箱、拆箱。

2、成员变量

  table:一个 Entry[] 数组类型
  count:Hashtable 的大小,它是 Hashtable 保存的键值对的数量。
  threshold:Hashtable 的阈值,用于判断是否需要调整 Hashtable 的容量。threshold 的值=”容量*加载因子”。
  loadFactor:加载因子。
  modCount:用来实现 fail-fast 机制的。

3、构造方法
//默认的构造器,将会以默认的初始容量(11)和默认的平衡因子(0.75)来初始化内部的数组 
public Hashtable();

//用指定的初始容量和默认的平衡因子(0.75)来初始化内部的数组
public Hashtable(int initialCapacity);

//用指定的初始容量和指定的平衡因子来初始化内部的数组
public Hashtable(int initialCapacity, float loadFactor);

//用一个Map对象来构建,并将该Map中的元素添加到Hashtable
public Hashtable(Map<? extends K, ? extends V> t);
4、成员方法概览
//置空操作
public synchronized void clear();

//查看该value是否在Hashtable中存在
public synchronized boolean contains(Object value);

//查看该key是否在Hashtable中存在
public synchronized boolean containsKey(Object key);

//该方法与contains()方法一样
public boolean containsValue(Object value);

//返回“所有value”的枚举对象
public synchronized Enumeration<V> elements();

//
public Set<Map.Entry<K,V>> entrySet();

//重写equals()函数
public synchronized boolean equals(Object o);

//返回key对应的value,没有的话返回null
public synchronized V get(Object key);

//
public synchronized int hashCode();

//返回该Hashtable是否为空
public synchronized boolean isEmpty();

//返回此哈希表中的键的枚举。 
public synchronized Enumeration<K> keys();

//返回该Hashtable中包含所有键的 Set 视图。
public Set<K> keySet();

//将指定 key 映射到此哈希表中的指定 value。 
public synchronized V put(K key, V value);

//将指定映射的所有映射关系复制到此哈希表中,这些映射关系将替换此哈希表拥有的、针对当前指定映射中所有键的所有映射关系。 
public synchronized void putAll(Map<? extends K, ? extends V> t);

//从哈希表中移除该键及其相应的值。
public synchronized V remove(Object key);

// 返回此哈希表中的键的数量。
public synchronized int size();

//返回此 Hashtable 对象的字符串表示形式,其形式为 ASCII 字符 ", " (逗号加空格)分隔开的、括在括号中的一组条目。 
public synchronized String toString();

//返回该Hashtable中 值的Collection视图
public Collection<V> values();
5、特点

  1.value不能为null

二 源码解析

1、hash算法解析
private int hash(Object k) {
// hashSeed will be zero if alternative hashing is disabled.
return hashSeed ^ k.hashCode();
}

  其中initHashSeedAsNeeded方法用于初始化hashSeed参数,其中hashSeed用于计算key的hash值,它与key的hashCode进行按位异或运算。这个hashSeed是一个与实例相关的随机值,主要用于解决hash冲突。

2、hash值到内存地址映射算法解析
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
3、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;
           }
       }

       modCount++;
       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.
       Entry<K,V> e = tab[index];
       tab[index] = new Entry<>(hash, key, value, e);
       count++;
       return null;
   }
4、扩容操作
protected void rehash() {
       int oldCapacity = table.length;
       Entry<K,V>[] oldMap = table;

       // overflow-conscious code
       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;

       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;
           }
       }
   }

   Hashtable的扩容操作,首先重新计算新的容量大小,为旧的容量大小的二倍加1。然后新new一个新的Entry数组,并且计算新的阀值。最后将旧数组中的Entry转移到新的Entry数组中。

5、线程安全的实现

  Hashtable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下Hashtable的效率非常低下。因为当一个线程访问Hashtable的同步方法时,其他线程访问Hashtable的同步方法时,可能会进入阻塞或轮询状态。
  注:Hashtable本身的操作是线程安全的,但是如果Hashtable中key所对应的值是一个对象的引用,对此对象进行修改操作无法自动保证线程安全。

三 遍历方式

1、使用keys()
Enumeration<String> en1 = table.keys();
    while(en1.hasMoreElements()) {
    en1.nextElement();
}
2、使用elements()
Enumeration<String> en2 = table.elements();
    while(en2.hasMoreElements()) {
    en2.nextElement();
}
3、使用keySet()
Iterator<String> it1 = table.keySet().iterator();
    while(it1.hasNext()) {
    it1.next();
}
4、使用entrySet()
Iterator<Entry<String, String>> it2 = table.entrySet().iterator();
    while(it2.hasNext()) {
    it2.next();
}

四 应用

1、Hashtable与HashMap的比较

  1)HashMap 的 key 和 value 都允许为 null,而 Hashtable 的 key 和 value 都不允许为 null。HashMap 遇到 key 为 null 的时候,调用 putForNullKey 方法进行处理,而对 value 没有处理;Hashtable遇到 null,直接返回 NullPointerException。
  2)Hashtable 方法是同步,而HashMap则不是。我们可以看一下源码,Hashtable 中的几乎所有的 public 的方法都是 synchronized 的,而有些方法也是在内部通过 synchronized 代码块来实现。所以有人一般都建议如果是涉及到多线程同步时采用 HashTable,没有涉及就采用 HashMap。
  3)HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值