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. MySQL——备份与恢复

    MySQL数据库备份和恢复 --备份类型: 1.热备份(只有InnoDB支持)(不影响业务,最好的方式) 2.冷备份( 阻止用户访问) 3.温备份 --备份方式: 1.逻辑备份(文本表示:SQL语句) ...

  2. 这款打怪升级的小游戏,7 年前出生于 GitHub 社区,如今在谷歌商店有 8 万人打了满分

    今天我在 GitHub 摸鱼寻找新的"目标"时,发现了一个开源项目是 RougeLike 类的角色扮演游戏「破碎版像素地牢」(Shattered Pixel Dungeon)类似魔 ...

  3. Spring Cloud Apollo 实践

    接上一篇Windows下安装Apollo的常见问题,安装完毕后试着看怎么来使用一下. 首先到管理页面创建一个新的应用: 创建成功后会自动跳转到应用的维护界面,如下图所示: 新增一个配置信息来进行后续的 ...

  4. djangoProject default codes

    1 === 2 settings.py 3 === 4 """ 5 Django settings for djangoProject project. 6 7 Gene ...

  5. 【第九篇】- Git 标签之Spring Cloud直播商城 b2b2c电子商务技术总结

    Git 标签 如果你达到一个重要的阶段,并希望永远记住那个特别的提交快照,你可以使用 git tag 给它打上标签. 比如说,我们想为我们的 xxx 项目发布一个"1.0"版本. ...

  6. 支持Cron表达式、间隔时间的工具(TaskScheduler)

    后台任务如何支持间隔时间.Cron表达式两种方式? 分享一个项目TaskScheduler,这是我从Furion项目中拷出来的 源码:https://gitee.com/dot-net-core/ta ...

  7. 【Python机器学习实战】决策树与集成学习(七)——集成学习(5)XGBoost实例及调参

    上一节对XGBoost算法的原理和过程进行了描述,XGBoost在算法优化方面主要在原损失函数中加入了正则项,同时将损失函数的二阶泰勒展开近似展开代替残差(事实上在GBDT中叶子结点的最优值求解也是使 ...

  8. 洛谷P1449——后缀表达式(栈模拟)

    题目描述 所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级). 如:3*(5–2)+7对应 ...

  9. 使用 VSCode 给STM32配置一个串口 printf 工程

    使用 VSCode 给STM32配置一个串口 printf 工程 gcc 重定向 printf 和 keil 不一样. 文件准备 先从以前的工程中拷过一份串口的代码来,然后在 main 函数中初始化串 ...

  10. 让PHP能够调用C的函数-FFI扩展

    在大型公司中,一般会有很我编程语言的配合.比如说让 Java 来做微服务层,用 C++ 来进行底层运算,用 PHP 来做中间层,最后使用 JS 展现效果.这些语言间的配合大部分都是通过 RPC 来完成 ...