Hashtable集合基本介绍和源码剖析

本文深入解析了Java中的Hashtable类,它是一个线程安全的键值存储容器,不允许键值为null。内容包括Hashtable的基本操作、线程安全性、扩容机制以及与HashMap的对比。通过实例展示了Hashtable的使用,包括添加、替换和删除元素的过程,以及在达到特定阈值时如何进行扩容。此外,还探讨了Hashtable的源码实现,包括Entry内部类和rehash方法。
摘要由CSDN通过智能技术生成
Hashtable的基本介绍
  1. 存放的元素是键值对象:即K-V
  2. Hashtable的键和值都不能存放null,否则会报空指针异常
  3. Hashtable使用方法基本上和HashMap一样
  4. Hashtable是线程安全的,HashMap是线程不安全的
  5. 扩容机制是以2倍再加1的方式进行扩容
int newCapacity = (oldCapacity << 1) + 1;

构造器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jpFpv1Zk-1647574668295)(E:\Typora笔记\java笔记\img\image-20220318105829961.png)]

常用方法

Modifier and TypeMethod and Description
voidclear() 清除此散列表,使其不包含键。
Objectclone() 创建这个散列表的浅拷贝。
Vcompute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 尝试计算指定键的映射及其当前映射的值(如果没有当前映射, null )。
VcomputeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) 如果指定的键尚未与某个值相关联(或映射到 null ),则尝试使用给定的映射函数计算其值,并将其输入到此映射中,除非 null
VcomputeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 如果指定的密钥的值存在且非空,则尝试计算给定密钥及其当前映射值的新映射。
booleancontains(Object value) 测试一些键映射到这个哈希表中的指定值。
booleancontainsKey(Object key) 测试指定的对象是否在此哈希表中的键。
booleancontainsValue(Object value) 如果此哈希表将一个或多个键映射到此值,则返回true。
Enumeration<V>elements() 返回此散列表中值的枚举。
Set<Map.Entry<K,V>>entrySet() 返回此地图中包含的映射的Set视图。
booleanequals(Object o) 根据Map界面中的定义,将指定的对象与此Map进行比较以相等。
voidforEach(BiConsumer<? super K,? super V> action) 对此映射中的每个条目执行给定的操作,直到所有条目都被处理或操作引发异常。
Vget(Object key) 返回到指定键所映射的值,或 null如果此映射包含该键的映射。
VgetOrDefault(Object key, V defaultValue) 返回到指定键所映射的值,或 defaultValue如果此映射包含该键的映射。
inthashCode() 按照Map界面中的定义返回此Map的哈希码值。
booleanisEmpty() 测试这个哈希表是否将值映射到值。
Enumeration<K>keys() 返回此散列表中键的枚举。
Set<K>keySet() 返回此地图中包含的键的Set视图。
Vmerge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) 如果指定的键尚未与值相关联或与null相关联,则将其与给定的非空值相关联。
Vput(K key, V value) 将指定的 key映射到此 key value中指定的value。
voidputAll(Map<? extends K,? extends V> t) 将所有从指定地图的映射复制到此散列表。
VputIfAbsent(K key, V value) 如果指定的键尚未与值相关联(或映射到 null )将其与给定值相关联并返回 null ,否则返回当前值。
protected voidrehash() 增加这个散列表的内部重组能力,从而更有效地适应和访问其条目。
Vremove(Object key) 从此散列表中删除键(及其对应的值)。
booleanremove(Object key, Object value) 仅当指定的密钥当前映射到指定的值时删除该条目。
Vreplace(K key, V value) 只有当目标映射到某个值时,才能替换指定键的条目。
booleanreplace(K key, V oldValue, V newValue) 仅当当前映射到指定的值时,才能替换指定键的条目。
voidreplaceAll(BiFunction<? super K,? super V,? extends V> function) 将每个条目的值替换为对该条目调用给定函数的结果,直到所有条目都被处理或该函数抛出异常。
intsize() 返回此哈希表中的键数。
StringtoString() 以一组条目的形式返回此 Hashtable对象的字符串表示形式,其括在大括号中,并以ASCII字符“ , ”(逗号和空格)分隔。
Collection<V>values() 返回此地图中包含的值的Collection视图。

Hashtable底层源码剖析

在使用无参构造器创建对象时,会在底层创建一个大小为 11 的table = new Entry<?,?>[initialCapacity]; 创建一个 Entry<?,?>的数组为 11数组

	
	步骤1:使用无参构造器 创建一个Hashtable 对象
	public 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; 对加载因子赋值为 0.75
        table = new Entry<?,?>[initialCapacity]; 创建一个 Entry<?,?>的数组为 11
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }
    
    步骤2public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {判断 value时是否为null,如果为null抛出异常
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table; 将 table 数组【本质是一个 Enty<?,?>[]数组】赋给 tab
        int hash = key.hashCode();获取一个哈希值
        int index = (hash & 0x7FFFFFFF) % tab.length;计算出在数组中索引位置
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];获取当前索引位置的元素是,是否为null,如果不为null,则进入for进行相关的判断 
        for(; entry != null ; entry = entry.next) {
        如果要添加的元素和原来的元素 哈希值和equals值,相同时,则将原来的value替换掉,并返回
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }
    
    调用addEntry 方法进行添加
    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++;
    	}
        
        调用 Hashtable 类中静态内部类 Entry 完成初始化
        private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Entry<K,V> next;

        protected Entry(int hash, K key, V value, Entry<K,V> next) {
            this.hash = hash;
            this.key =  key;
            this.value = value;
            this.next = next;
        }
        
        
        protected void rehash() {
        int oldCapacity = table.length;记录当前表的长度
        Entry<?,?>[] oldMap = table;并将表赋给 一个 oldMap 值

        // overflow-conscious code
        int newCapacity = (oldCapacity << 1) + 1;// oldCapacity乘2+1
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        
        创建一个新 Entry 数组 
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;记录
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

		将其移动到新的 Entry 数组中
        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;
            }
        }
    }

Hashtable扩容机制

package com.hspedu.map_;

import java.util.Hashtable;


@SuppressWarnings({"all"})
public class HashTableExercise {
    public static void main(String[] args) {
        Hashtable table = new Hashtable();//ok
        table.put("john", 100); //ok
        //table.put(null, 100); //异常 NullPointerException
        //table.put("john", null);//异常 NullPointerException
        table.put("lucy", 100);//ok
        table.put("lic", 100);//ok
        table.put("lic", 88);//替换
        table.put("hello1", 1);
        table.put("hello2", 1);
        table.put("hello3", 1);
        table.put("hello4", 1);
        table.put("hello5", 1);
        table.put("hello6", 1);
        System.out.println(table);

        //简单说明一下Hashtable的底层
        //1. 底层有数组 Hashtable$Entry[] 初始化大小为 11
        //2. 临界值 threshold 8 = 11 * 0.75
        //3. 扩容: 按照自己的扩容机制来进行即可.
        //4. 执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry
        //5. 当 if (count >= threshold) 满足时,就进行扩容
        //5. 按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.

    }
}
HashtableHashMap对比
版本线程安全【同步】效率允许null键null值
HashMap2.0不安全允许
Hashtable1.0安全较低不允许

注意是:HashMap键值都可以存储null值,但是key只能有一个,value可以有多个 Hashtable键值都不可以存储null

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yTjWv0DF-1647574668297)(E:\Typora笔记\java笔记\img\image-20220222164722130.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值