1类签名与注释

public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable

该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键值或值。

为了从散列表成功存储和检索对象,用作键的对象必须实现hashCode方法和equals方法。

与HashMap类似,两个影响Hashtable性能的参数: 初始容量和负载因子 。 容量是哈希表中的桶数, 初始容量只是创建哈希表时的容量。 请注意 :在“哈希冲突”的情况下,单个存储桶存储多个条目,必须依次搜索。 负载因子(默认是0.75)是在容量自动增加之前允许哈希表得到满足的度量。 关于何时以及是否调用rehash方法的具体细节是依赖于实现的。

所有这个类的“集合视图方法”返回的集合的iterator方法返回的迭代器是fail-fast的(fail-fast机制详见HashMap源码阅读)。

Hashtable是同步的。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable 。 如果需要线程安全的并发实现,那么建议使用ConcurrentHashMap代替Hashtable

我理解官方文档这句话的意思大概是:我们尽量不要用Hashtable,可以用HashMap或ConcurrentHashMap代替该类。接下来我们只解读基本的put、get与remove的代码。

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

put方法一开始就判断了value值不能为null,否则会报NullPointerException异常,但是没有判断key是否为空。我们写个代码测试一下:

Hashtable<Integer,String> ht = new Hashtable<>();
ht.put(null, "ouym");

运行上面代码也会报NullPointerException异常,因为line9调用了key.hashCode(),若key为null当然会报空指针了。

所以Hashtable是key和value都不能为null。

接下来看Hashtable是如何根据hash值定位的呢?line10如下

int index = (hash & 0x7FFFFFFF) % tab.length;

为什要& 0x7FFFFFFF ?(个人理解)0x7FFFFFFF=231,表示Integer的最大值。正常情况hash值应该是一个非负数,这种情况下hash & 0x7FFFFFFF的值与hash相等,但是不正常情况,例如求hash时越界的情况,hash变成了负数,hash & 0x7FFFFFFF等于求hash的补数(hash=-14,hash & 0x7FFFFFFF=231-14+1)。

Hashtable通过% tab.length来定位,此时不得不想起HashMap的设计精妙之处了。通过取模有两个性能问题,首先,当tab.length的值较小的时候,决定定位的是hash值的低位,高位并没有起到作用,直接的后果是容易冲突。其次,取模操作效率低于与操作(HashMap是与操作)。

然后检查key是否已经存在,若存在则覆盖已经存在的value,并返回旧的value。

其他都没有问题,最后调用addEntry插入新的元素。

3 get方法

public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
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;
}
}
return null;
}

定位对应的桶之后开始遍历链表,找到匹配的key对应的value并返回。

4 remove方法

 public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
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++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}

line10 prev不为null表示要删除的元素不是链表中的第一个,那么只需要将待删除元素的前一个元素next指向删除元素的next即可。若prev为null,表示链表的第一个元素是待删除元素,至于要将链表的表头指向下一个元素即可(line13)。

5总结

由于改类使用情况不多,所以只做基本方法的解读。但还有一些问题需要注意

(1)扩容问题

当前容量超过总容量*负载因子(默认0.75)时进行扩容,容量变为原来的两倍。并rehash

(2)为什么不推荐使用

一个原因是实现的效率问题,上述代码分析了定位的实现过程确实不如HashMap的高效。那么其较HashMap唯一的优点线程安全又在ConcurrentHashMap被实现了。

还有一个原因,Hashtable是基于古老的Dictionary实现的。Dictionary类已过时。 新的实现应该实现Map接口(如HashMap),而不是扩展这个类。

java源码阅读Hashtable的更多相关文章

  1. Java源码阅读的真实体会(一种学习思路)

    Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...

  2. Java源码阅读的真实体会(一种学习思路)【转】

    Java源码阅读的真实体会(一种学习思路)   刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...

  3. 如何阅读Java源码 阅读java的真实体会

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心.   说到技术基础,我打个比 ...

  4. [收藏] Java源码阅读的真实体会

    收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...

  5. Java源码阅读Stack

    Stack(栈)实现了一个后进先出(LIFO)的数据结构.该类继承了Vector类,是通过调用父类Vector的方法实现基本操作的. Stack共有以下五个操作: put:将元素压入栈顶. pop:弹 ...

  6. Java源码阅读顺序

    阅读顺序参考链接:https://blog.csdn.net/qq_21033663/article/details/79571506 阅读源码:JDK 8 计划阅读的package: 1.java. ...

  7. java源码阅读LinkedBlockingQueue

    1类签名与简介 public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements Blocking ...

  8. java源码阅读ArrayBlockingQueue

    1类签名与简介 public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQ ...

  9. Java源码阅读HashMap

    1类签名与注释 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cl ...

随机推荐

  1. Spring容器整合WebSocket

    原链接:http://blog.csdn.net/canot/article/details/52575054 WebSocker是一个保持web客户端与服务器长链接的技术.这样在两者通信过程中如果服 ...

  2. 8.read读取控制台输入

    read(选项)(参数)选项:-p:指定读取值时的提示符-t:指定读取时等待的时间(秒),如果没有在指定的时间内输入,就不再等待了参数:变量:指定读取时的变量名

  3. 一次Ubuntu下的排雷记录

    起因 某天,发现一台服务器上出现了一个大量占用cpu资源的进程.尝试手动杀掉,但很快就会自动重新创建新的进程. 追查 用命令lsof -p 10316 查看其文件路径: 该进程文件夹/proc/103 ...

  4. [ 手记 ] Oracle 11g安装过程

    安装环境:    操作系统:Centos6.4 Desktop    主机名:oracle    内存:2G    安装前准备:    修改主机名: [root@oracle ~]# vim /etc ...

  5. 理解OCI(Open Container Initiative)及docker的OCI实现(转)

    OCI定义了容器运行时标准,runC是Docker按照开放容器格式标准(OCF, Open Container Format)制定的一种具体实现. runC是从Docker的libcontainer中 ...

  6. Redis 源码走读(二)对象系统

    Redis设计了多种数据结构,并以此为基础构建了多种对象,每种对象(除了新出的 stream 以外)都有超过一种的实现. redisObject 这个结构体反应了 Redis 对象的内存布局 type ...

  7. 容斥原理 求M以内有多少个跟N是互质的

    开始系统的学习容斥原理!通常我们求1-n中与n互质的数的个数都是用欧拉函数! 但如果n比较大或者是求1-m中与n互质的数的个数等等问题,要想时间效率高的话还是用容斥原理!   本题是求[a,b]中与n ...

  8. POJ1861 Network(Kruskal)(并查集)

    Network Time Limit: 1000MS     Memory Limit: 30000K Total Submissions: 16047   Accepted: 6362   Spec ...

  9. RabbitMQ (四) 工作队列之公平分发

    上篇文章讲的轮询分发 : 1个队列,无论多少个消费者,无论消费者处理消息的耗时长短,大家消费的数量都一样. 而公平分发,又叫 : 能者多劳,顾名思义,处理得越快,消费得越多. 生产者 public c ...

  10. [CF819B]Mister B and PR Shifts

    题意:定义一个排列$p_{1\cdots n}$的“偏移量”$D=\sum _{i=1}^n\left|p_i-i\right|$ 求它所有的轮换排列中偏移量最小的是多少,要求输出轮换序数 暴力就是求 ...