吃透Java集合系列十:HashTable

2024-09-03 01:48

本文主要是介绍吃透Java集合系列十:HashTable,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一:整体实现

  • HashTable和HashMap实现大致相同,都是基于哈希表来实现的,数组+链表的形式(和HashMap有稍微的区别,HashMap加入了红黑树),它存储的内容是键值对(key-value)映射。
  • Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。Dictionary是一个过时的键值对映射的抽象类,jdk已经不建议使用,新的实现应该实现Map接口,而不是扩展这个类。
  • HashTable方法加了synchronized关键字,所以是线程安全的。

二:重要字段

private transient Entry<?,?>[] table;
private transient int count;
private int threshold;
private float loadFactor;
private transient int modCount = 0;

table实现哈希表的数组结构,数组长度最小为1,默认为11,里面存储着Entry

private static class Entry<K,V> implements Map.Entry<K,V> {final int hash;//哈希值,用于定位数组索引位置final K key;//键V value;//值Entry<K,V> next;//链表下一个元素}

Entry是HashTable的一个内部类,实现了Map.Entry接口,本质是就是一个映射(键值对)。

count是HashTable中实际存在的键值对数量,而modCount字段主要用来记录HashTable内部结构发生变化的次数,主要用于迭代的快速失败。强调一点,内部结构发生变化指的是结构发生变化,例如put新键值对,但是某个key对应的value值被覆盖不属于结构变化。

loadFactor为负载因子(默认值是0.75),threshold是HashTable所能容纳的最大数据量的Entry(键值对)个数。threshold = length * loadFactor。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。

threshold就是在此loadFactor和length(数组长度)对应下允许的最大元素数目,超过这个数目就重新rehash(扩容),扩容后的HashTable容量是之前容量的两倍+1。默认的负载因子0.75是对空间和时间效率的一个平衡选择,建议不要修改,除非在时间和空间比较特殊的情况下,如果内存空间很多而又对时间效率要求很高,可以降低负载因子loadFactor的值;相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。

三:扩容机制

当前键值对的数量count >= threshold时会触发扩容。

protected void rehash() {int oldCapacity = table.length;Entry<?,?>[] oldMap = table;//新容量=旧容量 * 2 + 1int 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;}//新建一个size = newCapacity的数组Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];modCount++;//重新计算阀值threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);table = newMap;//将原来的元素拷贝到新的HashTable中,对数组链表数据进行重新 hash index 计算,//rehash之后会使得最早插入的数据回到链表的第一位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,同时需要将原来HashTable中的元素一一复制到新的HashTable中,并且对每个元素根据hash值从新计算下标,这个过程是比较消耗时间的。

四:put方法

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

public synchronized V put(K key, V value) {// 值不能为空if (value == null) {throw new NullPointerException();}//计算key的hash值,确认在table[]中的索引位置Entry<?,?> tab[] = table;int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")Entry<K,V> entry = (Entry<K,V>)tab[index];//迭代index索引位置,如果该位置处的链表中存在一个一样的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;}}//如果不存在,则创建entry加入hash表中addEntry(hash, key, value, index);return null;}
//在指定索引位置加入key-value键值对
private void addEntry(int hash, K key, V value, int index) {modCount++;Entry<?,?> tab[] = table;//如果当前元素的数量大于等于阈值,则触发扩容if (count >= threshold) {// Rehash the table if the threshold is exceededrehash();tab = table;hash = key.hashCode();index = (hash & 0x7FFFFFFF) % tab.length;}// 创建entry并加入到链表的头@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>) tab[index];tab[index] = new Entry<>(hash, key, value, e);count++;}

五:get方法

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

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

六:HashTable和HashMap区别

  • HashMap线程不安全,HashTable是线程安全的。HashMap内部实现没有任何线程同步相关的代码,所以相对而言性能要好一点。如果在多线程中使用HashMap需要自己管理线程同步。HashTable大部分对外接口都使用synchronized包裹,所以是线程安全的,但是性能会相对差一些。
  • 二者的基类不一样。HashMap派生于AbstractMap,HashTable派生于Dictionary。它们都实现Map, Cloneable, Serializable这些接口。AbstractMap中提供的基础方法更多,并且实现了多个通用的方法,而在Dictionary中只有少量的接口,并且都是abstract类型。
  • key和value的取值范围不同。HashMap的key和value都可以为null,但是HashTable key和value都不能为null。对于HashMap如果get返回null,并不能表明HashMap不存在这个key,如果需要判断HashMap中是否包含某个key,就需要使用containsKey这个方法来判断。
  • 算法不一样。HashMap的initialCapacity为16,而HashTable的initialCapacity为11。HashMap中初始容量必须是2的幂,如果初始化传入的initialCapacity不是2的幂,将会自动调整为大于出入的initialCapacity最小的2的幂。HashMap使用自己的计算hash的方法(会依赖key的hashCode方法),HashTable则使用key的hashCode方法得到。

这篇关于吃透Java集合系列十:HashTable的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

SpringBoot使用ffmpeg实现视频压缩

《SpringBoot使用ffmpeg实现视频压缩》FFmpeg是一个开源的跨平台多媒体处理工具集,用于录制,转换,编辑和流式传输音频和视频,本文将使用ffmpeg实现视频压缩功能,有需要的可以参考... 目录核心功能1.格式转换2.编解码3.音视频处理4.流媒体支持5.滤镜(Filter)安装配置linu

在Spring Boot中实现HTTPS加密通信及常见问题排查

《在SpringBoot中实现HTTPS加密通信及常见问题排查》HTTPS是HTTP的安全版本,通过SSL/TLS协议为通讯提供加密、身份验证和数据完整性保护,下面通过本文给大家介绍在SpringB... 目录一、HTTPS核心原理1.加密流程概述2.加密技术组合二、证书体系详解1、证书类型对比2. 证书获

Java使用MethodHandle来替代反射,提高性能问题

《Java使用MethodHandle来替代反射,提高性能问题》:本文主要介绍Java使用MethodHandle来替代反射,提高性能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录一、认识MethodHandle1、简介2、使用方式3、与反射的区别二、示例1、基本使用2、(重要)

Java实现本地缓存的常用方案介绍

《Java实现本地缓存的常用方案介绍》本地缓存的代表技术主要有HashMap,GuavaCache,Caffeine和Encahche,这篇文章主要来和大家聊聊java利用这些技术分别实现本地缓存的方... 目录本地缓存实现方式HashMapConcurrentHashMapGuava CacheCaffe

SpringBoot整合Sa-Token实现RBAC权限模型的过程解析

《SpringBoot整合Sa-Token实现RBAC权限模型的过程解析》:本文主要介绍SpringBoot整合Sa-Token实现RBAC权限模型的过程解析,本文给大家介绍的非常详细,对大家的学... 目录前言一、基础概念1.1 RBAC模型核心概念1.2 Sa-Token核心功能1.3 环境准备二、表结

eclipse如何运行springboot项目

《eclipse如何运行springboot项目》:本文主要介绍eclipse如何运行springboot项目问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目js录当在eclipse启动spring boot项目时出现问题解决办法1.通过cmd命令行2.在ecl

Java中的Closeable接口及常见问题

《Java中的Closeable接口及常见问题》Closeable是Java中的一个标记接口,用于表示可以被关闭的对象,它定义了一个标准的方法来释放对象占用的系统资源,下面给大家介绍Java中的Clo... 目录1. Closeable接口概述2. 主要用途3. 实现类4. 使用方法5. 实现自定义Clos

Jvm sandbox mock机制的实践过程

《Jvmsandboxmock机制的实践过程》:本文主要介绍Jvmsandboxmock机制的实践过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、背景二、定义一个损坏的钟1、 Springboot工程中创建一个Clock类2、 添加一个Controller

SpringBoot实现文件记录日志及日志文件自动归档和压缩

《SpringBoot实现文件记录日志及日志文件自动归档和压缩》Logback是Java日志框架,通过Logger收集日志并经Appender输出至控制台、文件等,SpringBoot配置logbac... 目录1、什么是Logback2、SpringBoot实现文件记录日志,日志文件自动归档和压缩2.1、

MQTT SpringBoot整合实战教程

《MQTTSpringBoot整合实战教程》:本文主要介绍MQTTSpringBoot整合实战教程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录MQTT-SpringBoot创建简单 SpringBoot 项目导入必须依赖增加MQTT相关配置编写