HashTable 源码解析 jdk1.8
一、结构图
Hashtable采用桶位+链表结构实现,如下图所示:
二、属性
2.1 table
底层是一个存储Entry的数组
private transient Entry<?,?>[] table;
2.2 Entry
一个单向链表结构
private static class Entry<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Entry<K,V> next;protected Entry(int hash, K key, V value, Entry<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}@SuppressWarnings("unchecked")protected Object clone() {return new Entry<>(hash, key, value,(next==null ? null : (Entry<K,V>) next.clone()));}// Map.Entry Opspublic K getKey() {return key;}public V getValue() {return value;}public V setValue(V value) {if (value == null)throw new NullPointerException();V oldValue = this.value;this.value = value;return oldValue;}public boolean equals(Object o) {if (!(o instanceof Map.Entry))return false;Map.Entry<?,?> e = (Map.Entry<?,?>)o;return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&(value==null ? e.getValue()==null : value.equals(e.getValue()));}public int hashCode() {return hash ^ Objects.hashCode(value);}public String toString() {return key.toString()+"="+value.toString();}}
2.3 相关参数
//记录了存储的元素数量
private transient int count;//元素数量超过这个值后,会执行数组扩容
private int threshold;//负载因子
private float loadFactor;
三、方法
3.1 构造方法
默认构造方法,构造一个大小为11,负载因子为0.75的HashTable
public Hashtable() {this(11, 0.75f);}
3.2 put
注意Key和Value都不能为空
//hashtable之所以是线程安全的,就是因为在put和get方法上使用了synchronized关键字进行修饰public synchronized V put(K key, V value) {//限制Value非空if (value == null) {throw new NullPointerException();}// Makes sure the key is not already in the hashtable.Entry<?,?> tab[] = table;//因为直接使用key.hashcode(),而没有像hashmap一样判断key是否是null,所以key为null时,调用key.hashcode()会出错,所以key也不能为空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;}}//如果table[index]所连接的链表上不存在相同的key,则通过addEntry() 方法将新结点加载到链表的开头addEntry(hash, key, value, index);return null;}
3.2.1 addEntry
private void addEntry(int hash, K key, V value, int index) {modCount++;Entry<?,?> tab[] = table;//如果加入新结点后,hashtable中元素的个数超过了阈值threshold,则利用rehash()对数组进行扩容if (count >= threshold) {// Rehash the table if the threshold is exceededrehash();tab = table;hash = key.hashCode();index = (hash & 0x7FFFFFFF) % tab.length;}//将新结点放在原结点前,相当于把新结点放在table[index]位置,即整个链表的首部@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>) tab[index];tab[index] = new Entry<>(hash, key, value, e);count++;}
3.2.1.1 rehash
增加数组容量,并重新调整整个hashtable。
@SuppressWarnings("unchecked")protected void rehash() {int oldCapacity = table.length;Entry<?,?>[] oldMap = table;// overflow-conscious code//每次扩容,容量都变为原来的两倍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;}}}
3.3 get
如果存在键值对,那么返回key对应的值,否则返回null,返回的比较结果是通过equals返回的。
@SuppressWarnings("unchecked")//hashtable线程安全的愿意,synchronized关键字public synchronized V get(Object key) {Entry<?,?> tab[] = table;int hash = key.hashCode();//利用哈希值进行寻址int index = (hash & 0x7FFFFFFF) % tab.length;//遍历table[index]链表,找到key值相同的结点的value并返回,注意这里同样使用了key的equals方法,所以key被应用于hashtable时,不仅要重写hashcode()方法,还要重写equals方法for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {if ((e.hash == hash) && e.key.equals(key)) {return (V)e.value;}}return null;}
四、Hashtable和HashMap的区别总结
- hashmap中key和value均可以为null,但是hashtable中key和value均不能为null。
- hashmap采用的是数组(桶位)+链表+红黑树结构实现,而hashtable中采用的是数组(桶位)+链表实现。
- hashmap中出现hash冲突时,如果链表节点数小于8时是将新元素加入到链表的末尾,而hashtable中出现hash冲突时采用的是将新元素加入到链表的开头。
- hashmap中数组容量的大小要求是2的n次方,如果初始化时不符合要求会进行调整,而hashtable中数组容量的大小可以为任意正整数。
- hashmap中的寻址方法采用的是位运算按位与,而hashtable中寻址方式采用的是求余数。
- hashmap不是线程安全的,而hashtable是线程安全的,hashtable中的get和put方法均采用了synchronized关键字进行了方法同步。
- hashmap中默认容量的大小是16,而hashtable中默认数组容量是11。
HashTable 源码解析 jdk1.8相关推荐
- HashMap源码解析(JDK1.8)
HashMap源码解析(JDK1.8) 目录 定义 构造函数 数据结构 存储实现源码分析 删除操作源码分析 hashMap遍历和异常解析 1. 定义 HashMap实现了Map接口,继承Abstrac ...
- hashtable源码解析
Hashtable 也就是哈希表,是个非常重要的概率,在剖析hashtable源码前,我先简单介绍一下hashtable的原理 哈希表概念 什么是哈希(hash又称散列)? 将任意长度的消息压缩到某一 ...
- Java集合-ArrayList源码解析-JDK1.8
◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...
- HashMap 源码解析(JDK1.8)
HashMap是由数组加链表的结合体.如下图: 图中可以看出HashMap底层就是一个数组结构,每个数组中又存储着链表(链表的引用) JDK1.6实现hashmap的方式是采用位桶(数组)+链表的方式 ...
- ArrayBlockingQueue源码解析(JDK1.8)
ArrayBlockingQueue是基于数组的先进先出的有界循环队列 同样我们还是先上类的关系图 从类的关系图中可以看出ArrayBlockingQueue继承一个抽象类和实现了两个接口,然后分别简 ...
- 吊打java面试官之 Hashtable详细介绍(源码解析)和使用示例
概要 前一章,我们学习了HashMap.这一章,我们对Hashtable进行学习. 我们先对Hashtable有个整体认识,然后再学习它的源码,最后再通过实例来学会使用Hashtable. 第1部分 ...
- 源码解析-深刻理解Hash HashTable HashMap原理及数据hash碰撞问题
HashMap 前言 Hash HashTable 开地址法 线性探测法 平方探测法 双重散列探测法 拉链法 哈希表优势 HashMap 变量介绍 初始容量和负载因子 红黑树和链表转化 HashMap ...
- ConcurrentHashMap源码解析——基于JDK1.8
ConcurrentHashMap源码解析--基于JDK1.8 前言 这篇博客不知道写了多久,总之就是很久,头都炸了.最开始阅读源码时确实是一脸茫然,找不到下手的地方,真是太难了.下面的都是我自己阅读 ...
- 万字长文|Hashtable源码深度解析以及与HashMap的区别
基于JDK1.8对Java中的Hashtable集合的源码进行了深度解析,包括各种方法.扩容机制.哈希算法.遍历方法等方法的底层实现,最后给出了Hashtable和HashMap的详细对比以及使用建议 ...
- 万字长文之JDK1.8的LinkedList源码解析
1.前言 上一篇文章我们看了List集合的数组实现JDK1.8的ArrayList 源码解析,走过路过不要错过,本篇文章我们将介绍 List 集合的链表实现 LinkedList. 2.整体架构 和 ...
最新文章
- 硬件巨头正在崛起,中国独占鳌头
- python在linux报错xe6,python出现SyntaxError: Non-ASCII character '\xe6' in file \的错误
- spring计划任务
- ssm配置多数据库支持
- 如何把关联性的告警智能添加到 Nagios 上?(2)
- php要字符串的后四位,php如何截取字符串后四位
- 去掉源代码里的debug标致
- 今天,我需要你的支持!
- 2021 王道考研 操作系统+习题讲解
- vant制作首页的加载中和暂无数据
- vs2010中svn使用教程_vs2010+ Ankhsvn使用详解
- python立即关机_Python之电脑好帮手—自动定时关机
- 快速上手!2021年字节跳动、阿里等大厂最全Android面试题,Android校招面试指南
- Nehe教程16课雾
- linux查询进程号是否存在启动脚本,Shell实现判断进程是否存在并重新启动脚本分享...
- 北邮智能车仿真培训(五)—— 数据可视化工具的使用
- 软件测试技术(五)软件测试流程
- iOS 通过定位获取常驻后台
- 商城项目14_商品新增vo抽取、修改vo、新增逻辑、代码的具体落地、SPU检测、SKU检测、流程图
- vivado Clocking_Wizard IP配置
热门文章
- java 枚举嵌套枚举_java – 如何使用枚举与分组和分组层次/嵌套
- Flink Next:Beyond Stream Processing
- OpenGL中 Canvas 性能分析
- Android支付实践(三)之银联支付功能(客户端+服务端)
- python文件处理——JSON格式文件
- php 小程序页面传参,关于微信小程序中页面之间传参的解析
- 平板竖屏_朱海舟吐槽iPad办公体验:大量竖屏应用缺乏适配
- pythonATM,购物车项目实战2,主函数入口
- go代码--数据结构
- python股票_python股票 - 随笔分类 - 無碼 - 博客园