zoukankan      html  css  js  c++  java
  • Java中的Hashtable实现方法

    首先,先上Hashtable.class中的代码,所有的Java实现方法都在这个文件中了。

    当然,对于这个文件的内容,你现在无需看懂它,如果你看懂了,我们下面也就没有意义讲解了。
    现在,来让我们回顾下,在Java中使用Hashtable中的方法:

      Hashtable使用的方法
    import java.util.Hashtable;
     
    public class test {
     
        public void useHashtable()
        {
            String key = "key";
            String value = "value";
            Hashtable ht = new Hashtable<String,String>();
            ht.put(key, value);
            ht.remove(key);
        }
    }

    接下来,让我们从上面的使用方法说起。
    对于,构造Hashtable的那一行,我们看看对应到Hashtable.class文件中的调用,如下:

      Hashtable初始化
    /**
     * Constructs a new, empty hashtable with a default initial capacity (11)
     * and load factor (0.75).
     */
    public Hashtable() {
    this(11, 0.75f);
    }
     
    /**
     * Constructs a new, empty hashtable with the specified initial
     * capacity and the specified load factor.
     *
     * @param      initialCapacity   the initial capacity of the hashtable.
     * @param      loadFactor        the load factor of the hashtable.
     * @exception  IllegalArgumentException  if the initial capacity is less
     *             than zero, or if the load factor is nonpositive.
     */
    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)(initialCapacity * loadFactor);
    }

    这里注意,当你使用无参的构造函数时,默认传入的initialCapacity=11,loadFactor=0.75.
    Ok,你一定想问initialCapacity和loadFactor都是什么?我们看到构造函数中有这么两句语句:

    table = new Entry[initialCapacity];
    threshold = (int)(initialCapacity * loadFactor);

    这里的table变量时一个Entry数组,Entry你可以认为是一条记录,所以从第一句,我们就可以知道Hashtable就是一个Entry的数组。那么既然是数组,为什么还要提出Hashtable的概念?这个问题先放放,我们先来看loadFactor,看threshold的那句,你就能明白,其实loadFactor是用来判断是否要扩建的。所以,我们可以认为这里,当Hashtable的75%被使用时,就扩建。
    现在让我们来解答,这个Entry数组为什么是Hashtable,而不仅仅是数组。让我们来看看Entry的定义:

      Entry的类定义
    /**
         * Hashtable collision list.
         */
        private static class Entry<K,V> implements Map.Entry<K,V> {
        int hash;
        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 Object clone() {
            return new Entry<K,V>(hash, key, value,
                      (next==null ? null : (Entry<K,V>) next.clone()));
        }
     
        // Map.Entry Ops
     
        public K getKey() {
            return key;
        }
     
        public V getValue() {
            return value;
        }
     
        public V setValue(V value) {
            if (value == null)
            throw new NullPointerException();
     
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }
     
        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
            return false;
            Map.Entry e = (Map.Entry)o;
     
            return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
               (value==null ? e.getValue()==null : value.equals(e.getValue()));
        }
     
        public int hashCode() {
            return hash ^ (value==null ? 0 : value.hashCode());
        }
     
        public String toString() {
            return key.toString()+"="+value.toString();
        }
        }

    这里,我们关注它的四个字段,hash、key、value、next。这里的hash也就是之所以叫Hashtable的原因之一。这个hash数值是根据特定的hash算法得出的,详细内容可以找相应的hash算法资料,这里就不介绍了。接下来说,next字段,next字段是当hash产生重复的时候,以链表保存新传入的hash值对应的key和value。看图说话,就应该很明白了。
    Hashtable结构图
    上面的图应该就已经很清楚了。列表和数组共存的方法。
    这里有个题外话,就是前段时间,有个关于post大量相同hash的数据,使得hashtable大量退化成链表。导致查询速度奇慢的问题。也就是因为这里的这个机制。具体,怎么会产生hash碰撞,这和语言本身采用的hash算法有关。
    看完了初始化,我们接下来看下Hashtable的put方法。代码如下:

      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<K,V>(hash, key, value, e);
        count++;
        return null;
        }

    其他地方都没有什么好说的,关键看for语句这里,这里就是查询到对应的Entry后,再将对应的value赋给链表中正确的对应项。
    紧接着的下面的if语句,就是扩建机制,也没有什么可以多说的。最后一段内容,就是如果原有的Hashtable中没有对应项,就等于新增。
    有了put的基础,我们再来看看remove的情况,你应该很容易就能看懂。

      remove方法的函数体
    /**
     * 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;
    }

    同样,这个也没有可以多说的地方,仍然是for语句。逻辑和put的差不多。就是找到对应的项,然后做操作。

    基本上整个Hashtable的实现也就如此了。

  • 相关阅读:
    java环境变量配置(Windows & Linux)
    转行自学编程的前提条件和能力
    IntelliJ IDEA 视频教程
    小孩都懂得用“头衔”来包装自己了,那么你呢?
    自创“乒乓球自嗨玩法”
    什么是npm以及npm基本命令
    hexo本地搭建以及在github远程部署
    如何下载Java-配置环境全教程
    图的存储结构以及遍历
    二叉树的存储结构以及遍历
  • 原文地址:https://www.cnblogs.com/duanxz/p/3040217.html
Copyright © 2011-2022 走看看