Android学习之路(七)---- Hashtable

Android学习之路(七)---- Hashtable

Hashtable

在这里主要参考Java8的代码,其他版本几乎没怎么改动。
主要看的流程是创建,添加,获取,其他方法如果需要自行查看源码

1. 数据结构

private static class Entry<K,V> implements Map.Entry<K,V> {
        //注意这里使用final修饰了,HashMap并没有
        final 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;
        }

2. 构造函数

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);
    }

    /**
     * Constructs a new, empty hashtable with the specified initial capacity
     * and default load factor (0.75).
     *
     * @param     initialCapacity   the initial capacity of the hashtable.
     * @exception IllegalArgumentException if the initial capacity is less
     *              than zero.
     */
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /**
     * Constructs a new, empty hashtable with a default initial capacity (11)
     * and load factor (0.75).
     */
    public Hashtable() {
        this(11, 0.75f);
    }
  1. 可以看出所有的构造函数都调用public Hashtable(int initialCapacity, float loadFactor),在这个了创建了哈希表(默认11,加载因子为0.75),与HashMap有所不同的是HasHMap哈希表初始化操作在put中,在构造函数中只是进行一些赋值。(上面所有探讨都是排除传入Map对象这一个构造函数)
    2

3. put(K key, V value)方法

可以看出put使用了synchronized,上锁的实例是本类,所有同一个实例进行操作时同步,有线程安全,但是Hashtable中操作哈希表的操作都加上了同一锁(这里解释锁有点错误,可能需要了解过synchronized),会影响性能。

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();
        //就将原始的 hash 值转换为一个有效的、非负的数组索引。
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        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;
    }

4. addEntry(int hash, K key, V value, int index)方法

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++;
    }

5. rehash()方法

此方法进行哈希表扩容

protected void rehash() {
		//记录老哈希表的值
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        //相比之前扩容两倍加一
        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 = 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;
            }
        }
    }

6. put(Object key)方法

public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        //计算位置
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

7. 总结

  1. Hashtable在new的时候就创建了,哈希表,而且数据结构为数组加链表,这与HasHMap有些不同。
  2. Hashtable的put方法,首先通过hash值取到位置,然后通过头插法插入链表中,这与Java7的HasHMap有些相似。添加节点addEntry()方法中,首先会判断填充是否达到阀值来确定是否扩充。如果扩充需要重新计算填入节点的位置。最后返回值是旧值。
  3. rehash()方法是哈希表的扩充方法。大概内容是扩充一倍加一(如果没有达到最大值Integer.MAX_VALUE - 8),然后创建一个新的哈希表来重新填充数据。
  4. Hashtable的get方法,比较简单,通过hash值取到哈希表位置,通过遍历链表获取值。
  5. Hashtable是线程安全,这是由于暴露的操作哈希表的方法都加上了synchronized
  6. 关于键不能为 null 的原因包括:
  1. 哈希算法: Hashtable 使用键的哈希码来确定存储位置。如果键为 null,那么它的哈希码就需要为 0。由于哈希表的实现使用取模运算,0 对于任何正整数 n 都会产生 0。这就会导致所有键为 null 的元素都会映射到哈希表的相同位置,降低了哈希表的效率。
  2. equals() 方法: Hashtable 在查找键时使用 equals() 方法来比较键的相等性。如果键为 null,在调用 equals() 方法时就会导致NullPointerException 异常。

一些优秀的算法

1. (hash & 0x7FFFFFFF) % tab.length;

在 Hashtable 中,(hash & 0x7FFFFFFF) % tab.length; 这行代码的目的是将传入的 hash 值转换为一个正数索引,并将其限制在 Hashtable 的数组 tab 的长度范围内。

hash & 0x7FFFFFFF:这是一个位与操作,它确保 hash 的最高位(符号位)被清零。0x7FFFFFFF 是一个32位整数,其二进制表示是31个1后跟一个0。因此,当 hash 与 0x7FFFFFFF 进行位与操作时,hash 的符号位会被清零,从而确保结果是一个非负整数。这是因为在Java中,整数是有符号的,而哈希表的索引通常是无符号的或正数。
% tab.length:取模操作确保得到的索引值在 tab.length 的范围内。这是非常重要的,因为数组索引不能超出数组的实际长度。
将这两个操作组合起来,(hash & 0x7FFFFFFF) % tab.length; 就将原始的 hash 值转换为一个有效的、非负的数组索引。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值