从源码层理解Hashtable中的put和get

首先我们先看put方法:将指定 key 映射到此哈希表中的指定 value。注意这里键key和值value都不可为空。

[java] view
plain
 copy

print?

  1. public synchronized V put(K key, V value) {
  2. // 确保value不为null
  3. if (value == null) {
  4. throw new NullPointerException();
  5. }
  6. /*
  7. * 确保key在table[]是不重复的
  8. * 处理过程:
  9. * 1、计算key的hash值,确认在table[]中的索引位置
  10. * 2、迭代index索引位置,如果该位置处的链表中存在一个一样的key,则替换其value,返回旧值
  11. */
  12. Entry tab[] = table;
  13. int hash = hash(key);    //计算key的hash值
  14. int index = (hash & 0x7FFFFFFF) % tab.length;     //确认该key的索引位置
  15. //迭代,寻找该key,替换
  16. for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
  17. if ((e.hash == hash) && e.key.equals(key)) {
  18. V old = e.value;
  19. e.value = value;
  20. return old;
  21. }
  22. }
  23. modCount++;
  24. if (count >= threshold) {  //如果容器中的元素数量已经达到阀值,则进行扩容操作
  25. rehash();
  26. tab = table;
  27. hash = hash(key);
  28. index = (hash & 0x7FFFFFFF) % tab.length;
  29. }
  30. // 在索引位置处插入一个新的节点
  31. Entry<K,V> e = tab[index];
  32. tab[index] = new Entry<>(hash, key, value, e);
  33. //容器中元素+1
  34. count++;
  35. return null;
  36. }

put方法的整个处理流程是:计算key的hash值,根据hash值获得key在table数组中的索引位置,然后迭代该key处的Entry链表(我们暂且理解为链表),若该链表中存在一个这个的key对象,那么就直接替换其value值即可,否则在将改key-value节点插入该index索引位置处。如下:

首先我们假设一个容量为5的table,存在8、10、13、16、17、21。他们在table中位置如下:

然后我们插入一个数:put(16,22),key=16在table的索引位置为1,同时在1索引位置有两个数,程序对该“链表”进行迭代,发现存在一个key=16,这时要做的工作就是用newValue=22替换oldValue16,并将oldValue=16返回。

在put(33,33),key=33所在的索引位置为3,并且在该链表中也没有存在某个key=33的节点,所以就将该节点插入该链表的第一个位置。

在HashTabled的put方法中有两个地方需要注意:

        1、HashTable的扩容操作,在put方法中,如果需要向table[]中添加Entry元素,会首先进行容量校验,如果容量已经达到了阀值,HashTable就会进行扩容处理rehash(),如下:

[java] view
plain
 copy

print?

  1. protected void rehash() {
  2. int oldCapacity = table.length;
  3. //元素
  4. Entry<K,V>[] oldMap = table;
  5. //新容量=旧容量 * 2 + 1
  6. int newCapacity = (oldCapacity << 1) + 1;
  7. if (newCapacity - MAX_ARRAY_SIZE > 0) {
  8. if (oldCapacity == MAX_ARRAY_SIZE)
  9. return;
  10. newCapacity = MAX_ARRAY_SIZE;
  11. }
  12. //新建一个size = newCapacity 的HashTable
  13. Entry<K,V>[] newMap = new Entry[];
  14. modCount++;
  15. //重新计算阀值
  16. threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
  17. //重新计算hashSeed
  18. boolean rehash = initHashSeedAsNeeded(newCapacity);
  19. table = newMap;
  20. //将原来的元素拷贝到新的HashTable中
  21. for (int i = oldCapacity ; i-- > 0 ;) {
  22. for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
  23. Entry<K,V> e = old;
  24. old = old.next;
  25. if (rehash) {
  26. e.hash = hash(e.key);
  27. }
  28. int index = (e.hash & 0x7FFFFFFF) % newCapacity;
  29. e.next = newMap[index];
  30. newMap[index] = e;
  31. }
  32. }
  33. }

在这个rehash()方法中我们可以看到容量扩大两倍+1,同时需要将原来HashTable中的元素一一复制到新的HashTable中,这个过程是比较消耗时间的,同时还需要重新计算hashSeed的,毕竟容量已经变了。这里对阀值啰嗦一下:比如初始值11、加载因子默认0.75,那么这个时候阀值threshold=8,当容器中的元素达到8时,HashTable进行一次扩容操作,容量 = 11* 2 + 1 =23,而阀值threshold=23*0.75
= 17,当容器元素再一次达到阀值时,HashTable还会进行扩容操作,一次类推。

     2、在计算索引位置index时,HashTable进行了一个与运算过程(hash & 0x7FFFFFFF)下面是计算key的hash值,这里hashSeed发挥了作用。

[java] view
plain
 copy

print?

  1. private int hash(Object k) {
  2. return hashSeed ^ k.hashCode();
  3. }

相对于put方法,get方法就会比较简单,处理过程就是计算key的hash值,判断在table数组中的索引位置,然后迭代链表,匹配直到找到相对应key的value,若没有找到返回null。

[java] view
plain
 copy

print?

  1. public synchronized V get(Object key) {
  2. Entry tab[] = table;
  3. int hash = hash(key);
  4. int index = (hash & 0x7FFFFFFF) % tab.length;
  5. for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
  6. if ((e.hash == hash) && e.key.equals(key)) {
  7. return e.value;
  8. }
  9. }
  10. return null;
  11. }
时间: 2024-10-28 10:53:54

从源码层理解Hashtable中的put和get的相关文章

storm源码之理解Storm中Worker、Executor、Task关系【转】

[原]storm源码之理解Storm中Worker.Executor.Task关系 Storm在集群上运行一个Topology时,主要通过以下3个实体来完成Topology的执行工作:1. Worker(进程)2. Executor(线程)3. Task 下图简要描述了这3者之间的关系:                                                    1个worker进程执行的是1个topology的子集(注:不会出现1个worker为多个topology服

Live555源码分析[2]:RTSPServer中的用户认证

http://blog.csdn.net/njzhujinhua @20140601 说到鉴权,这是我多年来工作中的一部分,但这里rtsp中的认证简单多了,只是最基本的digest鉴权的策略. 在Live555的实现中, 用户信息由如下类维护,其提供增删查的接口.realm默认值为"LIVE555 Streaming Media" class UserAuthenticationDatabase { public: UserAuthenticationDatabase(char con

源码方式向openssl中添加新算法完整详细步骤(示例:摘要算法SM3)【非engine方式】

openssl简介 openssl是一个功能丰富且自包含的开源安全工具箱.它提供的主要功能有:SSL协议实现(包括SSLv2.SSLv3和TLSv1).大量软算法(对称/非对称/摘要).大数运算.非对称算法密钥生成.ASN.1编解码库.证书请求(PKCS10)编解码.数字证书编解码.CRL编解码.OCSP协议.数字证书验证.PKCS7标准实现和PKCS12个人数字证书格式实现等功能. openssl采用C语言作为开发语言,这使得它具有优秀的跨平台性能.openssl支持Linux.UNIX.wi

安卓图表引擎AChartEngine(四) - 源码示例 嵌入Acitivity中的折线图

前面几篇博客中都是调用ChartFactory.get***Intent()方法,本节讲的内容调用ChartFactory.get***View()方法,这个方法调用的结果可以嵌入到任何一个Activity中,作为Activity的一部分. XYChartBuilder.java(源码分析见注释) [java] view plaincopy package org.achartengine.chartdemo.demo.chart; import java.io.File; import jav

boost.asio源码剖析(四) ---- asio中的泛型概念(concepts)

* Protocol(通信协议) Protocol,是asio在网络编程方面最重要的一个concept.在第一章中的levelX类图中可以看到,所有提供网络相关功能的服务和I/O对象都需要Protocol来确定一些细节. Protocol的约束摘要如下: 1 class protocol 2 { 3 public: 4 /// Obtain an identifier for the type of the protocol. 5 int type() const; 6 7 /// Obtain

对Mybatis3源码结构理解(每天不断完善中...)

一.mybatis简介 Mybatis是支持普通SQL查询查询.存储过程和高级映射的优秀持久层框架.Mybatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索.Mybatis使用简单的XML或注解用于配置和原始映射,将接口和java的POJOS(Plan old java Objects,普通的java对象)映射成数据库中的记录. 二.框架结构图 刚开始学习源码,有哪儿不对的地方还望指出,十分感谢! (20160810第一版,以mybatis-3.4.1为例) 三.暂无

从源码角度分析Android中的Binder机制的前因后果

前面我也讲述过一篇文章<带你从零学习linux下的socket编程>,主要是从进程通信的角度开篇然后延伸到linux中的socket的开发.本篇文章依然是从进程通信的角度去分析下Android中的进程通信机制. 为什么在Android中使用binder通信机制? 众所周知linux中的进程通信有很多种方式,比如说管道.消息队列.socket机制等.socket我们再熟悉不过了,然而其作为一款通用的接口,通信开销大,数据传输效率低,主要用在跨网络间的进程间通信以及在本地的低速通信.消息队列和管道

【转】【java源码分析】Map中的hash算法分析

全网把Map中的hash()分析的最透彻的文章,别无二家. 2018年05月09日 09:08:08 阅读数:957 你知道HashMap中hash方法的具体实现吗?你知道HashTable.ConcurrentHashMap中hash方法的实现以及原因吗?你知道为什么要这么实现吗?你知道为什么JDK 7和JDK 8中hash方法实现的不同以及区别吗?如果你不能很好的回答这些问题,那么你需要好好看看这篇文章.文中涉及到大量代码和计算机底层原理知识.绝对的干货满满.整个互联网,把hash()分析的

转:【Java集合源码剖析】Hashtable源码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/36191279 Hashtable简介 Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中. Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆.