java.util.Hashtable源码解析

来源:互联网 发布:怎么查看手机mac地址 编辑:程序博客网 时间:2024/04/30 01:42

1.java集合框架图


2.所属包

package java.util;

3.继承与实现关系

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

4.属性

  /**     * 存放Entry的数组     */    private transient Entry<K,V>[] table;    /**     * 哈希表中Entry的数量     */    private transient int count;    /**     * 哈希表的临界值 threshold =capacity*loadFactor     */    private int threshold;    /** * 哈希表的加载因子     */    private float loadFactor;    /**     * 被修改的次数     */    private transient int modCount = 0;    /**     * 默认的hash临界值     */    static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

5.Hashtable的数据结构

哈希表的核心就是根据元素求位置。而多个元素可能出现相同的位置,那么就叫冲突。解决冲突的常用方式:链地址法:将多个值的不同的哈希结果(哈希值)用数组进行存储,然后将产生相同哈希值的元素,以单向链表的形式进行存储。所以拉链法的套路就是数组+单链表。第二种解决方式是线性探测法:将多个值余上表长度,如果多个值产生相同的哈希值,那么就依次往下寻找位置,直到不冲突为止。线性探测法致命的缺点就是当数据量上来时,就会频繁的进行碰撞冲突,然后找到位置比较费劲。


/**     * 采用单向链表的方式进行连接     */    private static class Entry<K,V> implements Map.Entry<K,V> {        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;        }       。。。。。。    }


6.构造方法

/** 构造方法1     * 指定初始化容量和加载因子参数的构造方法     */    public Hashtable(int initialCapacity, float loadFactor) {//初始化容量小于0,则抛出非法参数异常        if (initialCapacity < 0)            throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);//加载因子小于0或者加载因子非数字,则抛出非法参数异常           if (loadFactor <= 0 || Float.isNaN(loadFactor))            throw new IllegalArgumentException("Illegal Load: "+loadFactor);//如果初始化容量为0,那么就初始化为1        if (initialCapacity==0)            initialCapacity = 1;        this.loadFactor = loadFactor;        table = new Entry[initialCapacity];        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);        useAltHashing = sun.misc.VM.isBooted() &&                (initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);    }

 /** 构造方法2     * 指定初始化容量,采用默认加载因子0.75的构造方法     */    public Hashtable(int initialCapacity) {        this(initialCapacity, 0.75f);    }

/** 构造方法3     * 构造一个初始化容量为11,加载因子为0.75的构造方法     */    public Hashtable() {        this(11, 0.75f);    }

/** 构造方法4     * 通过给定的Map来构造HashTable.     *     * @param t the map whose mappings are to be placed in this map.     * @throws NullPointerException if the specified map is null.     * @since   1.2     */    public Hashtable(Map<? extends K, ? extends V> t) {        this(Math.max(2*t.size(), 11), 0.75f);        putAll(t);    }

7.方法

hash方法:

//通过键来获取hash值    private int hash(Object k) {        if (useAltHashing) {            if (k.getClass() == String.class) {                return sun.misc.Hashing.stringHash32((String) k);            } else {                int h = hashSeed ^ k.hashCode();                // This function ensures that hashCodes that differ only by                // constant multiples at each bit position have a bounded                // number of collisions (approximately 8 at default load factor).                h ^= (h >>> 20) ^ (h >>> 12);                return h ^ (h >>> 7) ^ (h >>> 4);             }        } else  {            return k.hashCode();        }    }
putAll方法:

/**     * 采用同步的方式插入值     */    public synchronized void putAll(Map<? extends K, ? extends V> t) {        for (Map.Entry<? extends K, ? extends V> e : t.entrySet())            put(e.getKey(), e.getValue());    }
put方法:

/**     * 将指定 key 映射到此哈希表中的指定 value。键和值都不可以为 null。     */    public synchronized V put(K key, V value) {        // 如果插入的值为null,抛出空指针异常        if (value == null) {            throw new NullPointerException();        }        Entry tab[] = table;        int hash = hash(key);//获取下标索引        int index = (hash & 0x7FFFFFFF) % tab.length;//遍历单链表,如果存在hash值和键都相同,那么就新值替换旧值,返回旧值        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++;//如果表长超出了临界值,重新进行hash值计算,并重新设置Entry数组、hash值、下标索引        if (count >= threshold) {            rehash();            tab = table;            hash = hash(key);            index = (hash & 0x7FFFFFFF) % tab.length;        }        // 创建新的Entry        Entry<K,V> e = tab[index];        tab[index] = new Entry<>(hash, key, value, e);        count++;        return null;    }
rehash方法:

/**     * 增加此哈希表的容量并在内部对其进行重组,以便更有效地容纳和访问其元素。当哈希表中的键的数量超出哈希表的容量和加载因子时,自动调用此方法。      */    protected void rehash() {        int oldCapacity = table.length;        Entry<K,V>[] oldMap = table;        // 新容量为旧容量的二倍加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<K,V>[] newMap = new Entry[newCapacity];        modCount++;//由新容量计算临界值        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);        boolean currentAltHashing = useAltHashing;        useAltHashing = sun.misc.VM.isBooted() &&                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);        boolean rehash = currentAltHashing ^ useAltHashing;        table = newMap;//遍历旧表中的元素,将旧表中的元素都转移到新表中        for (int i = oldCapacity ; i-- > 0 ;) {//遍历拥有相同hash值的单向链表            for (Entry<K,V> old = oldMap[i] ; old != null ; ) {                Entry<K,V> e = old;                old = old.next;//重新通过键生成hash值                if (rehash) {                    e.hash = hash(e.key);                }//通过取模的方式来求下标索引                int index = (e.hash & 0x7FFFFFFF) % newCapacity;                e.next = newMap[index];                newMap[index] = e;            }        }    }
contains方法:

/**     * 以同步的方式判断hash表中是否包含指定的值     */    public synchronized boolean contains(Object value) {//如果值为空,则抛出空指针异常        if (value == null) {            throw new NullPointerException();        }        Entry tab[] = table;//遍历每一条单链表中的值,如果存在与输入的值相同,那么返回true        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;    }
containsValue方法:

/**     * 以同步的方式判断hash表中是否包含指定的值     */    public boolean containsValue(Object value) {        return contains(value);    }
containsKey方法:

/**     * 以同步的方式来判断hash表中是否存在输入的键     */    public synchronized boolean containsKey(Object key) {        Entry tab[] = table;//通过键获取对应的hash值        int hash = hash(key);//通过hash值获取对应的下标索引        int index = (hash & 0x7FFFFFFF) % tab.length;//根据索引获取的Entry遍历单链表,如果存在hash值和键都相同的,那么就返回true。        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {            if ((e.hash == hash) && e.key.equals(key)) {                return true;            }        }        return false;    }
get方法:

/**     * 使用同步的方式通过键获取值     */    public synchronized V get(Object key) {        Entry tab[] = table;//通过键获取hash值        int hash = hash(key);//通过取模操作来获取下标索引        int index = (hash & 0x7FFFFFFF) % tab.length;//遍历该索引对应的那条单链表,如果存在hash值和键都相同的Entry,就返回该Entry对应的值。        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;    }
remove方法:

/**     * 通过键获取hash值 * 通过hash进行取模获取下标索引 * 通过索引获取Entry遍历单链表,找到hash和key都相同的元素将其删除,并调整其前后关系。 * 返回删除的旧值     */    public synchronized V remove(Object key) {        Entry tab[] = table;//使用键获取对应的hash值        int hash = hash(key);//使用取模操作来获取下标索引        int index = (hash & 0x7FFFFFFF) % tab.length;//遍历该索引对应的单链表,遍历找到hash值和键都相同的点,删除之后需要调整单链表中节点的前后关系。        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++;/* prev变量赋值为e * 如果prev不为空,那么e的下一个节点为prev的下一个节点。 * 如果prev为空,那么e的下一个节点为作为单链表的第一个节点(也对应着Entry数组中的值)*/                if (prev != null) {                    prev.next = e.next;                } else {                    tab[index] = e.next;                }//删除一个值,长度减一                count--;                V oldValue = e.value;//将删除的节点e的值设置为空,没有了引用,这样便于编译器回收                e.value = null;                return oldValue;            }        }        return null;    }
clear方法:

/**     * 遍历表table,将元素置为空,长度设置为0     */    public synchronized void clear() {        Entry tab[] = table;        modCount++;        for (int index = tab.length; --index >= 0; )            tab[index] = null;        count = 0;    }


-----------------------------该源码为jdk1.7版本的