Java集合之Hashtable的实现原理

本文详细解析了Hashtable的工作原理,包括其内部实现方式、线程安全性及其与HashMap的主要区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java集合之Hashtable的实现原理

Hashtable是一个废弃的类,虽然基本上已经被弃用了,但是也有必要了解它的内部实现原理,尤其是跟HashMap对比的时候。和HashMap一样,Hashtable也是一个散列表,它存储的内容是键值对(key-value)映射。

Hashtable的实现原理跟HashMap的实现原理(Java 8之前)是一样的,里面的数据结构同样是一个数组+链表的结构。首先来看一下put的源码


/**
     * Maps the specified <code>key</code> to the specified
     * <code>value</code> in this hashtable. Neither the key nor the
     * value can be <code>null</code>. <p>
     *
     * The value can be retrieved by calling the <code>get</code> method
     * with a key that is equal to the original key.
     *
     * @param      key     the hashtable key
     * @param      value   the value
     * @return     the previous value of the specified key in this hashtable,
     *             or <code>null</code> if it did not have one
     * @exception  NullPointerException  if the key or value is
     *               <code>null</code>
     * @see     Object#equals(Object)
     * @see     #get(Object)
     */
    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry tab[] = table;
        int hash = key.hashCode();
        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();

            tab = table;
            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;
    }

首先判断key是否为空,为空,则抛出空指针异常(这里是跟HashMap不同的地方);不为空,则把key的hashCode值拿出来,跟数组的长度进行一个hash运算,得到了这个key在数组中的下标index。得到下标后就可以取出对应数组位置的链表,然后就遍历这个链表。遍历的时候比较一下链表的hash值是否等于刚才传进来key的hash值,如果相等,则比较链表中字段key是否等于key(equals比较,hashCode和值要相等),如果还是相等的话,那么就说明包含了这个值,然后把这个链表的value值替换掉,返回之前的value。如果遍历完链表都没找到对应的链表,则表明没有这个值,需要添加到数组或者链表中。首先把已保存的个数加1,并且判断是否大于扩容的阈值条件,如果大于,则进行扩容(2n+1的方式扩容)。扩容之后,把index位置的链表(基本上为空)赋值到一个新的链表中,然后重新创建一个链表,并且把刚才那个空的链表赋值到该链表的next字段中(也是链表)。并且把该链表赋值到数组的index位置,然后返回null。

再来看看如何取数据,看源码

/**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * <p>More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code (key.equals(k))},
     * then this method returns {@code v}; otherwise it returns
     * {@code null}.  (There can be at most one such mapping.)
     *
     * @param key the key whose associated value is to be returned
     * @return the value to which the specified key is mapped, or
     *         {@code null} if this map contains no mapping for the key
     * @throws NullPointerException if the specified key is null
     * @see     #put(Object, Object)
     */
    public synchronized V get(Object key) {
        Entry tab[] = table;
        int hash = key.hashCode();
        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;
    }

其实跟put方法是差不多的,首先计算在数组中的位置index,然后取出链表,比较链表的hash值以及key的hash值以及值,如果都相等则返回该链表的value,如果遍历完了都没找到,那么则返回空。取数据还是比较简单的。

再来看看如何移除数据,同样,看源码

/**
     * Removes the key (and its corresponding value) from this
     * hashtable. This method does nothing if the key is not in the hashtable.
     *
     * @param   key   the key that needs to be removed
     * @return  the value to which the key had been mapped in this hashtable,
     *          or <code>null</code> if the key did not have a mapping
     * @throws  NullPointerException  if the key is <code>null</code>
     */
    public synchronized V remove(Object key) {
        Entry tab[] = table;
        int hash = key.hashCode();
        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;
    }

移除的源码也简单,前面都是一样的,先找到那个链表,然后判断是否符合key的要求,如果符合,则判断它是否有前一个链表,如果有,则把它的下一个链表即next赋值给前一个链表的next,用来代替自己,然后返回当前链表的value;如果没有,则代表它是第一个链表,则把它下一个的链表放到数组下标为index的位置上,同时返回它的value。如果没有找到对应的key,则返回null。

以下是是否包含某个键值对的代码

    /**
     * Tests if some key maps into the specified value in this hashtable.
     * This operation is more expensive than the {@link #containsKey
     * containsKey} method.
     *
     * <p>Note that this method is identical in functionality to
     * {@link #containsValue containsValue}, (which is part of the
     * {@link Map} interface in the collections framework).
     *
     * @param      value   a value to search for
     * @return     <code>true</code> if and only if some key maps to the
     *             <code>value</code> argument in this hashtable as
     *             determined by the <tt>equals</tt> method;
     *             <code>false</code> otherwise.
     * @exception  NullPointerException  if the value is <code>null</code>
     */
    public synchronized boolean contains(Object value) {
        if (value == null) {
            throw new NullPointerException();
        }

        Entry tab[] = table;
        for (int i = tab.length ; i-- > 0 ;) {
            for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
                if (e.value.equals(value)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Tests if the specified object is a key in this hashtable.
     *
     * @param   key   possible key
     * @return  <code>true</code> if and only if the specified object
     *          is a key in this hashtable, as determined by the
     *          <tt>equals</tt> method; <code>false</code> otherwise.
     * @throws  NullPointerException  if the key is <code>null</code>
     * @see     #contains(Object)
     */
    public synchronized boolean containsKey(Object key) {
        Entry tab[] = table;
        int hash = key.hashCode();
        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 true;
            }
        }
        return false;
    }

从以上代码可以看出,不管是包含键还是值,最后都是要遍历链表,查找到是否有相等的值,如果有,则返回true,没有就返回false。

以上就是Hashtable几个重要方法的实现原理,有哪里不对的欢迎指正

从以上代码可以看到都有一个相同点,那就是每个方法都是synchronized,所以Hashtable是一个线程安全的类,但是这也导致了它的一个性能问题(可能就是因为这个原因导致弃用的),所以性能比HashMap慢很多。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值