java.util.Hashtable源码解析

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版本的





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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值