很多人都知道HashTable与HashMap的关系,HashTable是线程安全的,HashMap是非线程安全的。在介绍完HashMap之后,趁热介绍一下HashTable。在HashTable中没有像HashMap中那么多关于数据结构的内容。HashTable是线程安全的,因为其源码的方法里都带有synchronized,但是效率不高,如果想使用高性能的Hash结构,建议使用java.util.concurrent.ConcurrentHashMap

HashTable 存储的数据类型

HashTable的key和value都可以为空,在存储的过程中 key必须实现 hashCode()和equals()两个方法。

影响HashTable性能的两个参数

HashTable中的两个变量影响其性能:初始容量与负载系数(load factor)。

容量

指的时hashtable中桶的个数。桶其实就是单向的链表。hashtable 是允许hash 冲突的,单个桶(链表)可以存储多个entry。在定义HashTable的初始容量的大小时,要权衡是空间 和 重新hash运算(很耗时)之间的利弊。当初始的容量大于元素的最大个数时,将不会发生rehash运算,但是太大的初始容量意味着浪费了很多空间。如果能提前估算出要向hashTable中存很多值时,就要给一个适合的初始容量,因为在添加数据时如,果不需要rehash操作的话将会更快。

负载系数

指的是hashtable在自动扩容之前允许桶多满?默认的负载系数为0.75,增大可以减少每次扩容的大小,但是增加了查找所花费的时间。

数据结构

前面也提到了,HashTable内部存储了一个table数组,这个数组的每一个元素存储的都是链表的头。在存储数值时,定位存储位置是通过如下代码:

    int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length; // 去掉符号位的影响Entry<K,V> e = (Entry<K,V>)tab[index];

上面的代码就确定了当前key的节点位于哪个链表上,e 即链表头。如果在该链表中无法找到对应的key,则将当前的节点添加到链表的头部。

        Entry<K,V> e = (Entry<K,V>) tab[index];// 把链表的头部传进去,为了将new 出来的节点.next指向原来链表的头部tab[index] = new Entry<>(hash, key, value, e); 

rehash算法

rehash算法,也可以理解为扩容算法,当table装不下要存储的值的时候,这是后就需要扩容增加内部数组的长度,这下惨了,每个key存储到哪个链表中是和table.length有直接关系的,所以在扩容时,要把当前hashtable中存储的节点重新计算一遍存储位置,这就是前面提到的为什么rehash会很耗时。

protected void rehash() {int oldCapacity = table.length;Entry<?,?>[] oldMap = table;// overflow-conscious codeint newCapacity = (oldCapacity << 1) + 1; // 容量每次扩大一倍if (newCapacity - MAX_ARRAY_SIZE > 0) {if (oldCapacity == MAX_ARRAY_SIZE)// Keep running with MAX_ARRAY_SIZE bucketsreturn;newCapacity = MAX_ARRAY_SIZE;}Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];modCount++; //结构变化threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);table = newMap;for (int i = oldCapacity ; i-- > 0 ;) {for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {Entry<K,V> e = old; // 将当前的变量赋值给暂存变量old = old.next; // 继续获取链表的下一个节点,为下一次循环做准备int index = (e.hash & 0x7FFFFFFF) % newCapacity; // 计算当前节点在newMap中存储的位置// 每次插入数据都插入到链表的头e.next = (Entry<K,V>)newMap[index];  // 将当前节点的指针指向原来链表的头newMap[index] = e; // 将当且节点存入数组中}}
}

compute方法

这不是hashMap独有的,是Map接口定义的。放在这里讲的原因是:HashTable没什么好写的,正好从HashMap把这部分内容搬过来。

computeIfAbsent,computeIfPresent,compute 三个方法,这三个方法本质上都是根据给定的key更新当前map中的值,HashMap中也有同样的方法
下面是一个简短的例子

public static void main(String[] args) {
HashMap<String,Integer> map = new HashMap<>();
for (int i = 0; i< 10; i++) {
map.put(String.valueOf(i),i);
}
map.computeIfPresent(String.valueOf(5),new MyFunction()); // 如果存在则计算
System.out.println(map);map.computeIfAbsent(String.valueOf(20),new AbsentFunciton());  //如果不存在添加
System.out.println(map);map.compute(String.valueOf(8),new MyFunction());    //如果存在则计算,不存在添加
System.out.println(map);
}//上面要使用的接口实现
class MyFunction implements BiFunction {@Overridepublic Object apply(Object key, Object oldValue) {if (key.equals("5")) {return (Integer)oldValue + 3;}return oldValue;}
}class AbsentFunciton implements Function{@Overridepublic Object apply(Object key) {return key;}
}

下面对3个方法进行一下介绍

computeIfAbsent

根据给定的key 在hashtable中查找,如果找到了返回key对应的值,如果没找到,根据定义的计算功能,算出新值,如果新值不为空,添加到hashtable中
public synchronized V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {//计算功能不能为空Objects.requireNonNull(mappingFunction);// 缓存内部tableEntry<?,?> tab[] = table;// 根据给定的key 计算出hashint hash = key.hashCode();// 根据hash求出在数组第几个链上int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>)tab[index];// 如果在链表中找到,则返回旧值for (; e != null; e = e.next) {if (e.hash == hash && e.key.equals(key)) {// Hashtable not accept null valuereturn e.value;}}// 记录modCount 在计算时,不允许修改hashtable结构int mc = modCount;// 获得根据计算功能计算出的新值V newValue = mappingFunction.apply(key);if (mc != modCount) { throw new ConcurrentModificationException(); }// 如果新值不为空,添加到hashtable中if (newValue != null) {addEntry(hash, key, newValue, index);}// 返回新值return newValue;
}

computeIfPresent

根据给定的key在hashtable中查找,如果没找到,返回空,如果找到了,根据定义的功能,计算出新值,如果新值为 null,则将key对应的节点删除,如果不是空,更新节点值,最后返回新值。
public synchronized V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {//定义的计算功能不能为空Objects.requireNonNull(remappingFunction);//复制一份tableEntry<?,?> tab[] = table;// 根据hash计算在hashtable中的位置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)) {// 如果找到了,根据key、旧值和定义的功能计算出新值 int mc = modCount;V newValue = remappingFunction.apply(key, e.value);if (mc != modCount) {throw new ConcurrentModificationException();}// 要将新值赋值到原来的key上,如果新值为空,则要在链表上删除对应的节点,计数器-1if (newValue == null) {if (prev != null) {prev.next = e.next; } else {tab[index] = e.next;}modCount = mc + 1;count--;} else {e.value = newValue;}// 返回新值return newValue;}}// 如果在对应位置上的链表中没有找到,则返回空return null;
}

compute

思路就是有就更新,没有就添加,是上面两个的整合。
public synchronized V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {// 定义的计算功能不能为空Objects.requireNonNull(remappingFunction);Entry<?,?> tab[] = table;// 根据hash获取链表位置int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>)tab[index];如果根据key找到了对应的节点,更新对应的节点值,并返回根据功能计算的新值for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {if (e.hash == hash && Objects.equals(e.key, key)) {int mc = modCount;V newValue = remappingFunction.apply(key, e.value);if (mc != modCount) {throw new ConcurrentModificationException();}if (newValue == null) {if (prev != null) {prev.next = e.next;} else {tab[index] = e.next;}modCount = mc + 1;count--;} else {e.value = newValue;}return newValue;}}// 如果没有找到根据key 计算出新值,如果新值不为空添加到table中,返回计算的新值int mc = modCount;V newValue = remappingFunction.apply(key, null);if (mc != modCount) { throw new ConcurrentModificationException(); }if (newValue != null) {addEntry(hash, key, newValue, index);}return newValue;
}

转载于:https://www.cnblogs.com/arax/p/8206702.html

HashTable 源码解读相关推荐

  1. jdk1.8.0_45源码解读——Map接口和AbstractMap抽象类的实现

    jdk1.8.0_45源码解读--Map接口和AbstractMap抽象类的实现 一. Map架构 如上图: (01) Map 是映射接口,Map中存储的内容是键值对(key-value). (02) ...

  2. java基本集合源码解读-JDK8/11

    文章目录 前言 详尽的debugger底层查看源码配置 一.集合体系图 二.List类集合 2.1.1 ArrayList 2.1.2ArrayList底层源码分析 结论: 2.1.3 使用Array ...

  3. Bert系列(二)——源码解读之模型主体

    本篇文章主要是解读模型主体代码modeling.py.在阅读这篇文章之前希望读者们对bert的相关理论有一定的了解,尤其是transformer的结构原理,网上的资料很多,本文内容对原理部分就不做过多 ...

  4. Bert系列(三)——源码解读之Pre-train

    https://www.jianshu.com/p/22e462f01d8c pre-train是迁移学习的基础,虽然Google已经发布了各种预训练好的模型,而且因为资源消耗巨大,自己再预训练也不现 ...

  5. linux下free源码,linux命令free源码解读:Procps free.c

    linux命令free源码解读 linux命令free源码解读:Procps free.c 作者:isayme 发布时间:September 26, 2011 分类:Linux 我们讨论的是linux ...

  6. nodeJS之eventproxy源码解读

    1.源码缩影 !(function (name, definition) { var hasDefine = typeof define === 'function', //检查上下文环境是否为AMD ...

  7. Java源码详解三:Hashtable源码分析--openjdk java 11源码

    文章目录 注释 哈希算法与映射 线程安全的实现方法 put 操作 get操作 本系列是Java详解,专栏地址:Java源码分析 Hashtable官方文档:Hashtable (Java Platfo ...

  8. PyTorch 源码解读之即时编译篇

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 作者丨OpenMMLab 来源丨https://zhuanlan.zhihu.com/ ...

  9. Alamofire源码解读系列(九)之响应封装(Response)

    本篇主要带来Alamofire中Response的解读 前言 在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下.我更想同大家分享这些顶级框架在设计和编码层次究竟有哪些过人的地方?当然, ...

最新文章

  1. Java与C++Socket通讯注意
  2. 12月9日 php环境的安装和基本知识的学习
  3. J-LINK7 固件修复
  4. 恢复SQLSERVER被误删除的数据
  5. MFC中CString和int的转换
  6. python加密模块教程_Python加密模块的hashlib,hmac模块使用解析
  7. 【BZOJ1826】【tyvj2644】缓存交换,贪心+堆维护
  8. 电脑的基本按键功能有哪些?
  9. DXUT实战2:HLSL(withoutEffect)+D3D9+DXUT(june_2010) .
  10. curl post json_Go Web编程--解析JSON请求和生成JSON响应
  11. 各类电商购物网站商城系统静态网页html制作(含静态网站源码)
  12. toastr 简单使用(弹出提示)
  13. 转载 | 各种数据库JDBC下载
  14. php实现RSA加密解密
  15. FlashFXP用到的功能
  16. Mybatis Generator配置文件
  17. 性能分析 -- 各种毛刺
  18. android 永久root权限,安卓 实现永久性开启adb 的root权限
  19. python 微信机器人教程_Python创建微信机器人(附赠Python视频教程)
  20. alpha因子常见问题_多因子模型是否真的可以带来阿尔法(alpha)?

热门文章

  1. (转) Spring 3 报org.aopalliance.intercept.MethodInterceptor问题解决方法
  2. Jenkins --SVN
  3. C#基础加强_泛型的基本原理
  4. centos下编译安装curl拓展
  5. Iphone表视图的简单操作
  6. tensorflow gpu windows配置步骤教学
  7. FocusBI:租房分析可视化(PowerBI网址体验)
  8. Redis-3.2主从复制与集群搭建 推荐
  9. Docker认识基础
  10. linux下编译安装MySQL5.6新版本