HashTable

一、HashTable

1. 继承关系

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

继承Dictionary类(JDK较早提供的一个实现类),实现Map、Cloneable、 java.io.Serializable 接口,可以克隆、序列化

2. 基本属性

private transient Entry<K,V>[] table;
private transient int count; //结点个数
private int threshold; //阈值
private float loadFactor; //加载因子

int hash;
final K key;
V value;
ntry<K,V> next;

3.特点

  • 底层数据结构:数组+链表

  • 键不能重复、值可以重复

  • 键、值都不能为null

  • 键无序

4. 构造函数

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 = new Entry[initialCapacity]; //直接在构造函数中初始化
   threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); //计算阈值
   initHashSeedAsNeeded(initialCapacity);
}
public Hashtable(int initialCapacity) {
    this(initialCapacity, 0.75f);
}
public Hashtable() {
    this(11, 0.75f);
}
public Hashtable(Map<? extends K, ? extends V> t) {
    this(Math.max(2*t.size(), 11), 0.75f);
    putAll(t);
}

5. 增长方式
在这里插入图片描述
从源码中可以看出,HashTable中数组扩容后的新容量为2*table.length+1

6. 增删方式

put()方法添加元素(线程安全)

  • 判断value,若为null则抛出异常,HashTable中key,value不能为null,若key为null也会抛出空指针异常

  • 通过key进行哈希获取到key该存储的索引位置

  • 遍历该索引位置下的链表,判断key是否存在(存在条件:hash值相等,且key.equals)

  • 在存在该key的情况下,替换value且直接返回 旧的value

  • key不存在则进行新结点插入逻辑

    • 扩容条件:count > threshold
    • 新容量为 2 * table.length + 1
    • 将原哈希表中的数据进行重新哈希到新的hash表
    • 更新插入的key的新位置
    • 找到结点的新位置,创建Entry实体通过头插法将元素插入
public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) { // 键、值都不能为null
        throw new NullPointerException();
    }
    // Makes sure the key is not already in the hashtable.
    Entry tab[] = table;
    //通过key的哈希找到存储位置
    int hash = hash(key);
    int index = (hash & 0x7FFFFFFF) % tab.length;
   for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
       if ((e.hash == hash) && e.key.equals(key)) {
           V old = e.value;
           e.value = value;
           return old;
       }
    }

    modCount++;
    if (count >= threshold) {
    // Rehash the table if the threshold is exceeded
        rehash();
        hash = hash(key);
        index = (hash & 0x7FFFFFFF) % tab.length;
    }
    // Creates the new entry.
    Entry<K,V> e = tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
    return null;
}
protected void rehash() {
        int oldCapacity = table.length;
        Entry<K,V>[] oldMap = table;

        // overflow-conscious code
        int newCapacity = (oldCapacity << 1) + 1;
        if (newCapacity - MAX_ARRAY_SIZE > 0) { //newCapacity >
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<K,V>[] newMap = new Entry[newCapacity];

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        boolean rehash = initHashSeedAsNeeded(newCapacity);

        table = newMap;

        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                if (rehash) {
                    e.hash = hash(e.key);
                }
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = newMap[index];
                newMap[index] = e;
            }
        }
    }

remove()方法删除元素 (线程安全)

  • 通过key来哈希获取到存储索引位置;
  • 对该索引位置的链表进行遍历,找到key所对应的entry实体并进行删除;
public synchronized V remove(Object key) {
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                modCount++;
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    tab[index] = e.next;
                }
                count--;
                V oldValue = e.value;
                e.value = null;
                return oldValue;
            }
        }
        return null;
    }

get()方法获取元素

  • get()方法本身也具有线程安全性
  • 通过key来哈希获取到存储索引位置 通过key为null进行get操作也会有异常抛出
  • 遍历当前索引位置结点,判断是否相等(hash key),找到直接返回value,未找到返回null
public synchronized V get(Object key) {
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return e.value;
            }
        }
        return null;
    }
二、HashMap和HashTable的异同点
  • 相同点:

    • 底层数据结构:数组+链表
    • key都不能重复
    • 插入无序
    • 哈希过程:通过key进行hash
  • 不同点:

继承父类安全性扩容方式key、value是否可以为null数组默认容量单线程下效率哈希算法不同
HashMapAbstractMap非线程安全2*table.length可以16,且必须指定为2的指数级关系
HashTableDictionary线程安全2*table.length+1不可以11
  • 单线程下HashTable效率低的原因

简单来说就是用户态和内核态的切换是有耗时的

三、HashTable能保证线程安全的原理

HashTable对相应方法添加Synchronized关键字,该关键字是一种互斥锁;
互斥锁的目的是保证同一时刻只能有一个线程对资源的访问
在HashMap对get()、set()等一般方法添加Synchronized关键字,修饰的是类对象
,该对象调用put操作即为该HashTable对象添加了一个互斥锁,那么在同一时刻只能一个线程访问该HashTable,从而保证添加元素不会出现异常

四、HashMap不具有线程安全性,如何让其在多线程下具有线程安全性?

使用集合工具类 Collections 使HashMap具有线程安全性

Collections.synchronizedMap(hashMap);
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值