在咱们开讲源码之前,首先需要了解下什么是哈希表?

散列表(Hash table 又称哈希表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中的一个位置来访问记录,以加快查找的速度.这个映射函数就叫做散列函数,存放记录的数组叫做散列表.              —— 百度百科

如图:

在Java中HashTable以数组+链表来实现,相对于HashMap来说要简单得多.HashTable不同于HashMap,它内部不允许插入null值,同时它是线程安全的,所有的读写操作都进行了锁保护,但也难以避免的对读写效率产生了较大影响.因此在日常开发中为保证线程安全一般建议使用ConcurrentHashMap.

为啥Java中Hashtable中的t要小写? 这不符合驼峰命名规则啊

大意: Hashtable创建于Java1,而集合的统一命名规范是后来在Java2中建立的,而当时又发布了新集合来代替它,再加上大量Java程序使用Hashtable类,考虑到兼容问题不可能将Hashtable改为HashTable.同时Hashtable已经过时了,不建议在代码中使用.

源码分析

结构图

继承关系

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

Dictionary是JDK1.0里引入的抽象类,用于存储键/值对,作用和Map类似.注意Dictionary类已经过时了,在实际开发中,可以通过实现Map接口来完成存储键值对的功能.

类中属性

    /** 内部维护了一个 Entry 数组 */private transient Entry<?,?>[] table;/** 哈希表里的元素数量 */private transient int count;/** 触发扩容的阈值 */private int threshold;/** 加载因子 默认 0.75 */private float loadFactor;/** 记录 涉及到结构变化的次数(offer/remove/clear等)  */private transient int modCount = 0;/** 版本号 */private static final long serialVersionUID = 1421746759512286392L;

table数组里存的Entry实际上是一个单向链表,哈希表的键值对都是存在table里的.

 private static class Entry<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Entry<K,V> next;...}

构造函数

    public Hashtable() { this(11, 0.75f); }public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); }public Hashtable(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal Load: "+loadFactor);//初始容量最小为1if (initialCapacity==0) initialCapacity = 1;this.loadFactor = loadFactor;table = new Entry<?,?>[initialCapacity];threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);}

Hashtable的默认容量是11,loadFactor默认加载因子0.75.threshold
数组容量 * loadFactor.

核心函数

添加函数

public synchronized V put(K key, V value) {if (value == null) {throw new NullPointerException();}Entry<?,?> tab[] = table;int hash = key.hashCode();// &运算取正值,再取模计算位置int index = (hash & 0x7FFFFFFF) % tab.length;Entry<K,V> entry = (Entry<K,V>)tab[index];// 如果这个hash和key都已经存在了,就把原来的value替换掉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函数通过关键字synchronized保证了线程安全.第一行就表明了Hashtable中value都不能为null.通过hash & 0x7FFFFFFF来规避掉负数,再进行分配位置.如果key经常存在了,则覆盖旧值并返回旧值.核心通过addEntry来添加元素.

    private void addEntry(int hash, K key, V value, int index) {modCount++;Entry<?,?> tab[] = table;// 若当前容量已经大于 阈值 进行rehash扩容if (count >= threshold) {  // threshold = count * loadFlor// Rehash the table if the threshold is exceededrehash();tab = table;hash = key.hashCode();index = (hash & 0x7FFFFFFF) % tab.length;}// Creates the new entry.Entry<K,V> e = (Entry<K,V>) tab[index];// 最新插入的 排在最前面tab[index] = new Entry<>(hash, key, value, e);count++;}

addEntry函数 先会判断如果需要扩容 当前数量 >= 阈值,调用rehash进行扩容,否则链表叠加.

    protected void rehash() {int oldCapacity = table.length;Entry<?,?>[] oldMap = table;// 新容量为老容量的一倍+1,int 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;e.next = (Entry<K,V>)newMap[index];newMap[index] = e;}}}

rehash函数先会把容量扩大1倍+1,然后创建一个newMap,把oldMap里的元素遍历复制到新的newMap里,这个过程是比较耗时的,同时此操作后数组和链表里元素的位置都会发生改变.

删除函数

    public synchronized V remove(Object key) {Entry<?,?> tab[] = table;int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;Entry<K,V> e = (Entry<K,V>)tab[index];// 遍历链表 e 当前节点, prev 上一个节点  next 下一个节点for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {// 性能优化 先 进行hash 判断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;}

remove函数比较简单,通过key定位在数组中的位置,再遍历链表删除元素.

获取函数

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

get函数和remove函数比较类似,都是先通过key定位在数组中的位置,再迭代链表找到元素并直接返回.

迭代器

Hashtable里迭代器使用的是比较老的Enumeration接口,其作用和Iterator类似,只提供了遍历的功能.虽然Enumeration还未被废弃,但现在代码里已经很少使用了.本篇文章就不再讲述了,有兴趣的可以去翻翻代码~

public interface Enumeration<E> {/** 判断是否还有元素  */boolean hasMoreElements();/** 如果还有元素则返回下一个元素,否则抛NoSuchElementException异常   */E nextElement();
}

结语

Hashtable整体是比较简单的,其内部充斥着大量的遍历操作,当数据量大的时候操作会非常耗时,非特殊情况下是用不到的.
一般来说,在日常开发中非并发场景推荐使用HashMap,并发场景下虽然可以用Hashtable,但是更推荐使用ConcurrentHashMap.
ConcurrentHashMap内部虽然也是使用Synchronized,但它是针对单个对象的锁,相比于Hashtable里锁的粒度更细,效率更高.

一篇文章带你搞定HashTable相关推荐

  1. java 不重启部署_一篇文章带你搞定SpringBoot不重启项目实现修改静态资源

    一.通过配置文件控制静态资源的热部署 在配置文件 application.properties 中添加: #表示从这个默认不触发重启的目录中除去static目录 spring.devtools.res ...

  2. 一篇文章带你搞定 MongoDB 实现 REST

    文章目录 一.前期准备 二.SpringBoot 实现 MongoDB 的 REST 三.使用 Postman 测试使用 一.前期准备 首先使用 docker 搭建好 MongoDB:一篇文章带你搞定 ...

  3. 一篇文章带你搞定19年数学建模机场出租车优化问题示例讲解含代码

    文章目录 一.问题分析 二.数据介绍 三.模型的求解 四.结果分析 一.问题分析 收集国内某一机场及其所在城市出租车的相关数据,给出该机场出租车司机的选择方案,并分析模型的合理性和对相关因素的依赖性. ...

  4. 如何使用RSA 对数据加解密和签名验签?一篇文章带你搞定

    点击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 三分割据纡筹策,万古云霄一羽毛. ...

  5. 一篇文章带你搞定二维插值的 MATLAB 计算

    前面已经学习了二维插值的基本概念:一篇文章带你认识数学建模中的二维插值 本篇文章主要实现使用MATLAB进行二维插值计算 文章目录 一.网格节点的插值计算 二.散点数据的插值计算 1. 示例 1 2. ...

  6. 一篇文章带你搞定 SpringBoot 自定义欢迎页和网页图标 favicon

    文章目录 一.SpringBoot 自定义项目启动欢迎页 二.SpringBoot 自定义 favicon 一.SpringBoot 自定义项目启动欢迎页 已经分析过SpringBoot 的资源访问路 ...

  7. 一篇文章带你搞定 Ubuntu 中打开 Pycharm 总是卡顿崩溃

    由于 Ubuntu 中的汉字输入实在是太不友好了,所以装了个 搜狗输入法,好不容易把 搜狗输入法装好,本以为可以开开心心的搞代码了,然而... pycharm 一打开,就崩溃,关不掉,进程杀死还是不行 ...

  8. 一篇文章带你搞定 Java 日志框架 slf4j

    文章目录 一.门面模式 二.为什么要使用 slf4j ? 三.如何使用 一.门面模式 slf4j是门面模式的典型应用,因此在讲slf4j前,先简单学习下门面模式, 门面模式,其核心为外部与一个子系统的 ...

  9. 一篇文章带你搞定 HTTPS

    文章目录 一.HTTP加上加密处理和认证以及完整性保护后即是HTTPS 二.HTTPS是身披SSL外壳的HTTP 三.相互交换密钥的公开密钥加密技术 1. 共享密钥加密的困境 2. 使用两把密钥的公开 ...

最新文章

  1. Squid access.log 转发到其他syslog服务器(OSSIM)
  2. phpStorm 2016.1 最新版激活方法
  3. 禁止snmpd往syslog中写入无用信息
  4. Windows 下各种Python库的下载与安装
  5. python中变量类型在程序中可以改变_详细解析Python当中的数据类型和变量
  6. 1 java基础增强
  7. 6个座位办公室最佳位置_办公室座位最佳位置(讲解)
  8. ps去水印教程_叫板 PS!去水印、抠图、加滤镜,这款超强修图应用到底什么来头...
  9. RS232 9针串口定义
  10. 跑PIN码破解无线网络WIFI密码的原理分析(转)
  11. 《ERP高级计划》书解读-APS案例分析之四缓冲的计算(蔡颖)(转)
  12. P8198 [传智杯 #4 决赛] 背单词的小智 二分答案+前缀和
  13. 第十一课:磁场和洛伦兹力
  14. AD936x 系列快速入口
  15. 威洛特:狗狗乱咬东西都是有原因的
  16. 网络营销技巧:有必要为移动端专门做站吗?
  17. SLsec招新题wp
  18. dht11温湿度传感器特点及使用介绍
  19. python求解多元多次方程组或非线性方程组
  20. 关于AnyChat录像解决方案的几张原理图

热门文章

  1. Access-Control-Expose-Headers
  2. linux Ubuntu网卡配置,Windows 7下用VirtualBox安装Ubuntu网卡配置
  3. 转行做程序员之前你应该考虑的三件事
  4. jenkins Error performing command: git ls-remote -h
  5. 微信小程序仿系统自带下拉刷新效果
  6. 或非门sr锁存器_SR锁存器为什么可以实现存储的功能?
  7. 2小时07分30秒!尘封逾15年中国马拉松国家纪录被打破 | 美通社头条
  8. Fegin x-www-form-urlencoded 类型请求
  9. 为新机让路走降价路线的两部手机,市场竞争太激烈,旗舰机也扛不住
  10. 生成HTML报告时,报错:AttributeError: type object ‘_io.StringIO‘ has no attribute ‘StringIO‘