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. kernel_thread()和kthread_run()/kthread_create()的根本区别

    0 本质区别 kthread_run()调用kthread_create(), kthread_create()加入链表后,有kthreadd()线程读取链表然后再调用kernel_thread()创 ...

  2. LVS+keepalived集群

    一.Keepalived工具介绍专为LVS和HA设计的一款健康检查工具 支持故障自动切换(Failover) 支持节点健康状态检查(Health Checking) 官方网站:http://www.k ...

  3. 用python的pandas读取excel文件中的数据

    一.读取Excel文件   使用pandas的read_excel()方法,可通过文件路径直接读取.注意到,在一个excel文件中有多个sheet,因此,对excel文件的读取实际上是读取指定文件.并 ...

  4. HTML+CSS设计个人主页

    在个人主页的设计中,我采用了圣代布局和div分块.效果图如下: <!DOCTYPE html> <html lang="en"> <head> ...

  5. Ts基本数据类型

    TS的基本数据类型 string let str : string str = 1 //报错 会提示num是字符串 不是数字 str = 'test' //正确 //拼接字符串 let str2 : ...

  6. 开源的物联网技术平台(Thingsboard)

    1   总体说明 1.1   产品概述 1.1.1 Thingsboard作用 1.置备并控制设备. 2.采集设备数据并进行数据可视化. 3.分析设备数据,触发告警. 4.将数据传输到另一个系统. 5 ...

  7. Java从入门到精通(第5版)上半部分

    1.1java简介 先起了oak 橡树 这个名字,因为商标原因改为爪洼岛谐音的Java 一次编写,到处运行 java如何运行 java程序既是编译型又是解释型 Java版本 Java SE 标准版(开 ...

  8. PHP操作用户提交内容时需要注意的危险函数

    对于我们的程序开发来说,用户的输入是解决安全性问题的第一大入口.为什么这么说呢?不管是SQL注入.XSS还是文件上传漏洞,全部都和用户提交的输入参数有关.今天我们不讲这些问题,我们主要探讨下面对用户的 ...

  9. Centos8.X 搭建Grafana+Jmeter+Influxdb 性能实时监控平台

    前言 本篇文章引用了小菠萝测试笔记,大部分内容非原创,基于自身实操过程中,完善了部分. 本篇随笔是在Linux上搭建的,后面会补充在docker以及k8s上如何部署安装 工具介绍 工具 介绍 Jmet ...

  10. jmeter性能实战

    概述 性能测试: 通过并发工具请求服务器,提前发现性能问题,优化并解决 为什么做性能测试? 常规需求 用户反馈性能问题 项目对性能不放心 性能测试的最终目标:? 性能指标分析 多快好省 项目性能场景提 ...