Hashtable

目录

一、Hashtable简介

二、源码分析

        1)变量

        2)构造方法

       3) put方法

        4)rehash()方法

        5)remove()方法

        6)replace()方法

三、Hashtable和HashMap的区别?


一、Hashtable简介

        Hashtable的父类是Dictionary类,实现了Map<K,V>, Cloneable, java.io.Serializable接口。

        因为Hashtable的所有public方法都有synchronized关键字修饰,所以它是线程安全的散列表,Hashtable的键值对都不允许为空。

        Hashtable的底层数据结构:数组+链表

       

二、源码分析
        1)变量
    /**
     * 存储数据节点的数组
     */
    private transient Entry<?,?>[] table;

    /**
     * 在Hashtable中元素总量
     */
    private transient int count;

    /**
     * 扩容阈值
     */
    private int threshold;

    /**
     * 加载因子
     */
    private float loadFactor;

    /**
     * 操作次数
     */
    private transient int modCount = 0;
        2)构造方法
    
    /**
     * 默认的无参构造,如果使用此方法则会创建出一个容量为11,加载因子为0.75的Hashtable
     */
    public Hashtable() {
        this(11, 0.75f);
    }

    
    /**
     * 有参构造,可以主动设值合适的容量,防止频繁扩容降低效率
     */
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 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 = new Entry<?,?>[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

    /**
     * 有参构造,将某个map对象转化为Hashtable对象
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }
       3) put方法

                从源码中可以发现,Hashtable计算hash值的方式是直接调用对象的hashCode()方法生成hash值,与HashMap的不同的。并且计算对应下标的方式也不一样。

    /**
     * 往数组中插入元素,此方法给synchronized关键字修饰所以是线程安全的。
     */
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        // 这里确保value不是空的
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        // 确保即将插入的key不在hashtable中
        Entry<?,?> tab[] = table;
        // 直接调用对象的hashCode()方法生成hash值
        int hash = key.hashCode();
        // 将hash值与0x7FFFFFFF(这个是int类型的最大值)进行与运算再和数组长度求余获得数组下标
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        // 获取数组内前下标的节点
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        // 循环遍历链表,判断key是否重复,有就进行替换
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
        
        
        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++;
    }
        4)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;
        }
        // 创建新数组
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        // 记录操作次数
        modCount++;
        // 计算新的扩容阈值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        // 将新数组赋值给table
        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;
            }
        }
    }
        5)remove()方法
    /**
     * 线程安全的删除方法
     */
    public synchronized V remove(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        // 计算key对应的下标
        int index = (hash & 0x7FFFFFFF) % tab.length;

        // 循环遍历对应下标处的链表
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
        for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
            // 找到待删除的节点
            if ((e.hash == hash) && e.key.equals(key)) {
                modCount++;
                // 如果被删除节点不是头节点则将上一节点的next指向被删除节点的下一节点
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    // 如果是头节点则将被删除节点的下一节点放入数组中
                    tab[index] = e.next;
                }
                // Hashtable内元素个数减1
                count--;
                V oldValue = e.value;
                e.value = null;
                return oldValue;
            }
        }
        return null;
    }


    /**
     * 删除指定key,被删除的key对应的value需要与传入的value一致
     */
    @Override
    public synchronized boolean remove(Object key, Object value) {
        Objects.requireNonNull(value);

        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
        for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
            if ((e.hash == hash) && e.key.equals(key) && e.value.equals(value)) {
                modCount++;
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    tab[index] = e.next;
                }
                count--;
                e.value = null;
                return true;
            }
        }
        return false;
    }
        6)replace()方法
    /**
     * 替换指定节点的value
     */
    @Override
    public synchronized V replace(K key, V value) {
        Objects.requireNonNull(value);
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        // 计算代替换节点的下标
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
        // 循环遍历链表
        for (; e != null; e = e.next) {
            // 如果key相同则进行替换
            if ((e.hash == hash) && e.key.equals(key)) {
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }
        return null;
    }
三、Hashtable和HashMap的区别?

        1)Hashtable是线程安全的,HashMap是线程不安全的。

        2)默认容量,扩容倍数不同。Hashtable的默认容量是11,HashMap的默认容量是16。Hashtable对自定义的初始容量没有要求,HashMap对自定义的初始容量要求是2的幂次方。Hashtable的扩容倍数是两倍+1,HashMap是两倍。

        3)继承的父类不同,Hashtable继承DIctionary,HashMap继承AbstractMap。

        4)计算hash值、下标的方式不同。

        5)底层使用的数据结构不同,Hashtable是:数组+链表,HashMap是:数组+链表+红黑树

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值