Java:HashTable类小记

对 Java 中的 HashTable类,做一个微不足道的小小小小记

概述

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

需要说明的是:HashTable 和 HashMap 的实现原理基本一样,差别无非是:

  1. HashTable 不允许 key 和 value 为 null;

  2. HashTable 是线程安全的。

    但是 HashTable 线程安全的策略实现代价却太大了,简单粗暴,get/put 所有相关操作都是 synchronized 的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。

  3. 扩容方式稍有不同,见后续分析

对于 HashMap 的一点记录,见:Java:HashMap类小记

相同点

对比了 HashTable 与 HashMap 的相同点,如下:

  1. 都可以用来存储键值对
  2. 底层哈希表结构查询速度都很快
  3. 内部通过单链表解决冲突问题,容量不足会自动增加
  4. 都实现了 Map 接口
  5. 都实现了 Serializable 接口,支持序列化
  6. 实现了 Cloneable 接口,可以被克隆

不同点

  1. HashTable 继承了 Dictionary 类,而 HashMap 是继承了 AbstractMap类。Dictionary 是任何可将键映射到相应值的类的抽象父类,而 AbstractMap 是基于 Map 接口的实现,它以最大限度地减少实现此接口所需的工作。

    public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    ...
    }
    public abstract class AbstractMap<K,V> implements Map<K,V> {
    ...
    } /************************************************/ public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {
    ...
    } public abstract
    class Dictionary<K,V> {
    ...
    }

    据说这是因为:历史原因

  2. 线程安全不一样:Hashtable 是线程安全的,而 HashMap 不是线程安全的,但是我们也可以通过 Collections.synchronizedMap(hashMap),使其实现同步。

    //这是Hashtable的put()方法:
    public synchronized V put(K key, V value){
    ...
    } /************************************************/ //这是HashMap的put()方法:
    public V put(K key, V value) {
    ...
    }

    从上面的源代码可以看到 Hashtable 的 put() 方法是synchronized的,而 HashMap 的 put() 方法却不是。

  3. HashMap 的 key 和 value 都允许为 null,而 Hashtable 的 key 和 value 都不允许为 null。

    HashMap 遇到 key 为 null 的时候,调用 putForNullKey 方法进行处理(JDK 8中在hash函数中做了判断),而对 value 没有处理;

    Hashtable 遇到 null,直接返回 NullPointerException

    // HashMap
    public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
    } // 在 hash(key)方法中:对于null做了特殊的处理
    static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    } /************************************************/ // Hashtable
    public synchronized V put(K key, V value) {
    // Make sure the value is not null
    // 1.值为null直接抛出异常
    if (value == null) {
    throw new NullPointerException();
    }
    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    // 由于key是null,null当然是没有hashCode()这种方法的
    // 因此直接抛出异常NullPointerException
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    // ....
    }
  4. 遍历方式的内部实现上不同:HashMap使用 Iterator 遍历,HashTable 使用Enumeration 遍历;

    据说这也是因为:历史原因

    关于 Iterator 与 Enumeration 的异同,见:Java:Iterator接口与fail-fast小记

  5. HashMap 和 HashTable 的初始化方式扩容方式不同:

    HashMap:在构造函数中不创建数组,而是在第一次put时才创建,且初始大小为16;之后每次扩充容量为原来的两倍;

    HashTable:在构造函数直接创建hashtable,且其初始大小为11,之后每次扩充2n+1

    // hashmap初始化:HashMap hashMap = new HashMap();
    // 默认构造函数
    public HashMap() {
    // 先不创建,在使用的时候再创建
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    // 其中:DEFAULT_LOAD_FACTOR = 0.75f;
    // DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    } public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
    } /************************************************/ // hashtable初始化:Hashtable hashtable = new Hashtable();
    public Hashtable() {
    // 构造函数直接创建hashtable
    this(11, 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); if (initialCapacity==0)
    initialCapacity = 1;
    this.loadFactor = loadFactor;
    // 始化table,获得大小为initialCapacity的table数组
    table = new Entry<?,?>[initialCapacity];
    // 计算阀值
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    } // hastable扩容:
    public synchronized V put(K key, V value) {
    // ... // 添加元素,见下面
    addEntry(hash, key, value, index);
    return null;
    } 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 exceeded
    rehash(); tab = table;
    hash = key.hashCode();
    index = (hash & 0x7FFFFFFF) % tab.length;
    } // Creates the new entry.
    @SuppressWarnings("unchecked")
    // 添加元素
    Entry<K,V> e = (Entry<K,V>) tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
    } // rehash,扩容操作
    protected void rehash() {
    int oldCapacity = table.length;
    Entry<?,?>[] oldMap = table; // overflow-conscious code
    // 这里:新容量=旧容量 * 2 + 1
    int newCapacity = (oldCapacity << 1) + 1;
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
    if (oldCapacity == MAX_ARRAY_SIZE)
    // Keep running with MAX_ARRAY_SIZE buckets
    return;
    newCapacity = MAX_ARRAY_SIZE;
    }
    // 新建一个size = newCapacity 的HashTable
    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;
    }
    }
    }

参考

https://blog.csdn.net/u010983881/article/details/49762595

https://mp.weixin.qq.com/s/q1r9Pno6ANUzZ9wMzA-JSg

Java:HashTable类小记的更多相关文章

  1. Java Hashtable类

    哈希表(Hashtable)是原来的java.util中的一部分,是一个字典的具体实现. 然而,Java2重新设计的哈希表,以便它也实现了​​Map接口.因此,哈希表现已集成到集合框架.它类似于Has ...

  2. Java:ConcurrentHashMap类小记-1(概述)

    Java:ConcurrentHashMap类小记-1(概述) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentHas ...

  3. Java:ConcurrentHashMap类小记-2(JDK7)

    Java:ConcurrentHashMap类小记-2(JDK7) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentH ...

  4. Java:TreeMap类小记

    Java:TreeMap类小记 对 Java 中的 TreeMap类,做一个微不足道的小小小小记 概述 前言:之前已经小小分析了一波 HashMap类.HashTable类.ConcurrentHas ...

  5. Java:ConcurrentHashMap类小记-3(JDK8)

    Java:ConcurrentHashMap类小记-3(JDK8) 结构说明 // 所有数据都存在table中, 只有当第一次插入时才会被加载,扩容时总是以2的倍数进行 transient volat ...

  6. Java:HashMap类小记

    Java:HashMap类小记 对 Java 中的 HashMap类,做一个微不足道的小小小小记 概述 HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致.由于要保证键的唯一.不重复 ...

  7. Java:LinkedHashMap类小记

    Java:LinkedHashMap类小记 对 Java 中的 LinkedHashMap类,做一个微不足道的小小小小记 概述 public class LinkedHashMap<K,V> ...

  8. Java:LinkedList类小记

    Java:LinkedList类小记 对 Java 中的 LinkedList类,做一个微不足道的小小小小记 概述 java.util.LinkedList 集合数据存储的结构是循环双向链表结构.方便 ...

  9. Java:ArrayList类小记

    Java:ArrayList类小记 对 Java 中的 ArrayList类,做一个微不足道的小小小小记 概述 java.util.ArrayList 是大小可变的数组的实现,存储在内的数据称为元素. ...

随机推荐

  1. window 日志的查看与清理

    日志查看: 启动Windows实验台,点击:开始 - 控制面板 - 管理工具 - 事件查看器.如下图所示. 2.在事件查看器中右键应用程序(或安全性.系统.DNS服务器)查看属性可以得到日志存放文件的 ...

  2. 【第四篇】-Git 工作区、暂存区和版本库之Spring Cloud直播商城 b2b2c电子商务技术总结

    Git 工作区.暂存区和版本库 基本概念 我们先来理解下 Git 工作区.暂存区和版本库概念: 工作区:就是你在电脑里能看到的目录. 暂存区:英文叫 stage 或 index.一般存放在 .git  ...

  3. 安卓gradle时报错"ERROR: Plugin with id 'com.android.application' not found."

    在build.gradle中更改gradle插件版本号 buildscript { repositories { google() jcenter() } dependencies { //版本号请根 ...

  4. chrome浏览器中安装以及使用Elasticsearch head 插件

    一.安装Elasticsearch head 插件 下载安装包:https://github.com/liufengji/es-head/commit/121cdcb6d1b18656461e4889 ...

  5. 「含源码」关于NXP IMX8 Mini的图形开发指南(GPU)案例分享!

    前言 Graphical Demo框架提供了对平台相关依赖的抽象.Graphical应用的通用封装,如模型加载.纹理加载.着色器编译等,以及其它一些通用的应用逻辑处理的封装,使得使用框架的开发人员(以 ...

  6. Hive On Spark保姆级攻略

    声明: 此博客参考了官网的配置方式,并结合笔者在实践网上部分帖子时的踩坑经历整理而成 这里贴上官方配置说明: [官方]: https://cwiki.apache.org//confluence/di ...

  7. IE浏览器设置兼容性

    因为IE浏览器不兼容高版本的Jquery.Bootstrap等JS框架,导致页面在Google浏览器和在IE的显示完全不一样,所以需要对页面进行兼容性设置 <!--设置兼容性--> < ...

  8. Go语言中的并发编程

    并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因. Go语言中的并发编程 并发与并行 并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天) ...

  9. Jmeter压测学习4--XPath提取器

    没有遇到,直接转载:https://www.cnblogs.com/yoyoketang/p/11962553.html 前言 有些web项目是前后端不分离的,返回的内容不是那种纯进口返回json格式 ...

  10. Xamarin Android使用自签名证书

    背景 项目中后台web服务部署成https服务时,需要使用SSL证书,如果我们不使用公共的CA时,怎么办? 不仅如此,因为是小项目,App应用主要是小范围使用,此时只有IP地址,根本没有域名,怎么办? ...