HashTable 源码解读

很多人都知道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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/398983.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#Semaphore信号量用法

参考博客&#xff1a;http://www.cnblogs.com/free722/archive/2011/04/03/2004926.html http://www.cnblogs.com/heqichang/archive/2011/12/24/2300301.html 信号量说简单点就是为了线程同步&#xff0c;或者说是为了限制线程能运行的数量。 那它又是怎么限制线程的数量的哩&…

OC基础--OC中的类方法和对象方法

PS:个人感觉跟C#的静态方法和非静态方法有点类似&#xff0c;仅仅是有点类似。明杰老师说过不要总跟之前学过的语言做比较&#xff0c;但是个人觉得&#xff0c;比较一下可以加深印象吧。重点是自己真的能够区分开&#xff01; 一、OC中的对象方法 1.以减号“-”开头 2.只能让对…

linux vnc 改端口号,RHEL6下配置vncserver服务(包括修改vnc端口)

RHEL6下配置vncserver服务(包括修改vnc端口)(2012-04-13 23:36:07)标签&#xff1a;it配置完vsftpd后&#xff0c;还必须要开的服务就是vnc啦&#xff0c;首先&#xff0c;在root用户下利用yum源安装vncserver:yum list | grep vncyum install tigervnc-server.i686安装完成后&…

简单Linq笔记

Linq是.net 3.5才引入的 要引入命名空间System.Linq. Linq to XML要引入System.Xml.Linq Linq to ADO.NET要引入System.Data.Linq 每个Linq查询都是以from子句开始,Linq查询中,select子句和select子句都是必备子句.Linq查询表达式 必须以select或group子句结束 from字句包括两…

Groovy在Spring中的简单使用实例

2019独角兽企业重金招聘Python工程师标准>>> 步骤一&#xff1a; 如果你使用的是Eclipse,则需先添加groovy插件&#xff0c;以便操作groovy文件。 可参&#xff1a;http://blog.csdn.net/haigenwong/article/details/22947075 步骤二&#xff1a; 步骤三&#xff1a…

linux6个服务级别,RHEL 6 和 RHEL 7 的一些有关运行级别,服务管理,服务启动等方面的区别介绍...

systemd是7中的新命令组&#xff0c;集成了service和chkconfig的功能。system命令可参考&#xff1a;https://www.cnblogs.com/ray-bk/p/10415173.html运行级别概念的区分System V init 运行级别 systemd 目标名称 作用0 …

.net core 2.0学习记录(一):搭建一个.Net Core网站项目

.Net Core开发可以使用Visual Studio 2017或者Visual Studio Code,下面使用Visual Studio 2017搭建一个.net Core MVC网站项目。 一.新建项目 二.选择 Web应用程序(模型视图控制器) 三.项目结构和之前的比对还是有很大的不同,wwwroot用来存放前端的一些静态资源(css/js/image/h…

一个APP的由来

之前在站酷、UI中国、优设等网站看过不少的APP教程、规范等一些东西。自认为有些规范讲的内容过于繁琐&#xff0c;对于像我这样的大多数设计师来说看着看着就懵逼了.... 如何联系我&#xff1a;【万里虎】www.bravetiger.cn 【QQ】3396726884 &#xff08;咨询问题100元起&…

【easy】234. Palindrome Linked List

ques: 判断一个链表是否回文 Could you do it in O(n) time and O(1) space? method&#xff1a;先将链表分为两部分&#xff0c;将后半部分反转&#xff0c;最后从前往后判断是否相等。 topic: 链表&#xff0c;链表反转 /** * Definition for singly-linked list. * public …

linux qt 添加.so,Linux环境下qt/qt creator添加OpenCV的配置

第一次使用qtcreator&#xff0c;我是做图像处理的&#xff0c;想在Ubuntu下将qtcreator和opencv的环境配置起来&#xff0c;着资料和尝试配置&#xff0c;耗费了我一个上午和一个下午&#xff0c;终于最终摸出了门路&#xff0c;以供大家分享。第一步&#xff1a;下载和安装op…

算法纲要

基本 枚举、贪心、递归、分治、递推、模拟 STL&#xff08;pair、vector、set、map、queue、string、algorithm&#xff09; 构造、位运算、常数优化 数据结构 队列、堆、栈、链表 排序&#xff08;插入、冒泡、快速、归并、堆、桶、基数&#xff09; 二分查找、散列表 并查集、…

Auto Layout 和 Constraints

文章修改 2月1日&#xff1a;添加使用约束、编辑约束和iOS特性三个部分2月24日&#xff1a;根据自己的理解&#xff0c;修改iOS特性部分的内容 自动布局Auto Layout Auto Layout&#xff0c;通过设置在View上的约束&#xff0c;动态计算视图层次结构中所有的View的尺寸和位置。…

哪个linux桌面有电池显示,(求助!!!)进ubuntu后桌面只显示左上角!(已解决)...

jonathan303 于 2008-10-24 18:23:45发表:G卡 好说了 因为你们有配置你的显卡文件 你在系统里-》有一个显卡的调节程序&#xff0c; 你打开的时候里面没有显示&#xff0c; 你进行一下操作&#xff1a;sudo dpkg-reconfigure xserver-xorg一路默认cltaltbackspace重启X执行步骤…

Linux NTP时间服务器

NTP 时间服务器 ntp也是一种协议 ntp软件&#xff08;支持ntp协议&#xff09; CentOS6自带 CentOS7需要安装 chrony软件&#xff08;支持ntp协议&#xff09; CentOS7自带 安装ntp CentOS6系统自带ntp&#xff0c;CentOS7需要自己安装 [rootm02 ~]# yum -y install ntp [ro…

Host 'xxx' is not allowed to connect to this MySQL server.

mysql开启远程连接 今天在服务器安装了mysql&#xff0c;准备用mysqlguitools远程登录的时候出错&#xff0c;提示&#xff1a;Host xxx is not allowed to connect to this MySQL server。网上找了一些资料&#xff0c;是mysql未开启mysql远程访问权限导致。 记录解决方案供以…

tkinter的GUI设计:界面与逻辑分离(三)-- 多页面

知识点&#xff1a; 使用 tkinter.Frame.tkraise() 函数去提升当前 tkinter.Frame 的 z 轴顺序&#xff0c;使得多个 tkinter.Frame 的可见性得以切换 本文基于&#xff1a;win7 python34 1 2 3 4 5 import matplotlib matplotlib.use("TkAgg")from matplotlib.back…

linux软件证券,linux

1.说明在*nix环境下&#xff0c;经常通过源码安装软件。./configure make make install基本上都是定式了。本文对于configure的基本原理进行阐述&#xff0c;并借助http://concurrencykit.org/开源包的configure文件&#xff0c;对于实际的运行过程进行分析。2.configure基本背…

0x08算法设计与分析复习(二):算法设计策略-回溯法2

参考书籍&#xff1a;算法设计与分析——C语言描述&#xff08;第二版&#xff09; 算法设计策略-回溯法 子集和数 问题描述 已知n个不同的正数wi(0≤i≤n−1)的集合&#xff0c;求该集合的所有满足条件的子集&#xff0c;使得每个子集中的正数之和等于另一个给定的正数M。 回溯…

Android源码解析--SwipeMenuListView仿QQ聊天左滑

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/50612714 绪论&#xff1a; 好久没写博客了&#xff0c;最近比较懒&#xff0c;不想写博客&#xff0c;但是在看书&#xff0c;看一些Android进阶的书&#…

UVA 125 Numbering Paths

题意 给出方向&#xff08;有向&#xff09;然后判断从一个点到另一个点的方案数。如果有无数条那么对应位置置为-1 直接先dp处理出来。dp[i][j] sum(dp[i][k]*dp[k][j]) 同时如果两点之间有无限条路径。那么这两点之间必然有一环存在。有f[k][k]!0 #include <map> #…