【Map篇】HashTable详解

7 篇文章 0 订阅

HashTable 是一种基于哈希函数的数据结构。它将每个键Key映射到一个唯一的索引Index,通过这个索引来快速访问数据。底层是一个数组,数组中的每个元素称为桶(bucket)。
当我们需要访问某个元素时,首先会对键进行哈希函数计算得到一个Hash值,再将Hash值进行位运算,最后和数组长度进行取模,得到它的索引。然后通过该索引找到相应的桶,最终在桶中找到对应的元素。
存在哈希碰撞的问题,即不同的键经过哈希函数计算后可能得到相同的索引,导致数据被存储在同一个桶中,HashTable 默认使用链表来解决哈希碰撞。
在这里插入图片描述
如何将Key映射成index的?

//先将key进行hash运算,得到一个hash值
int hash = key.hashCode();
//将hash值先进行位运算后,再与数组长度进行取模,得到索引index
int index = (hash & 0x7FFFFFFF) % tab.length;

在这里插入图片描述

源码分析(JDK1.8)

成员变量属性
/**
 * 初始Entry数组.
 */
private transient Entry<?,?>[] table;

/**
 * 记录table上Entry的个数
 */
private transient int count;

/**
 * 数组的扩容容量阀值,当超过这个阀值时,就需要进行扩容操作
 * (int)(容量*负载系数)
 */
private int threshold;

/**
 * 负载因子,默认0.75.
 */
private float loadFactor;

/**
 * 此哈希表在结构上被修改的次数
 * 结构修改是指更改哈希表或以其他方式修改其内部结构(例如:rehash)
 */
private transient int modCount = 0;
构造函数
  1. 创建一个空的HashTable,默认初始化大小为11,负载因子为0.75
public Hashtable() {
    this(11, 0.75f);
}
  1. 创建一个指定初始容量的HashTable,默认负载因子为0.75
public Hashtable(int initialCapacity) {
  this(initialCapacity, 0.75f);
}
  1. 创建一个指定初始容量和负载因子的HashTable
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;
    //创建一个初始容量的Entry[]数组
    table = new Entry<?,?>[initialCapacity];
    //计算扩容的阀值
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
  1. 创建一个包含指定Map中所有映射关系的新HashTable
public Hashtable(Map<? extends K, ? extends V> t) {
    //创建一个指定容量大小,负载因子0.75的数组
    //容量大小: 两倍的Map元素个数,11相比,取大值
    this(Math.max(2*t.size(), 11), 0.75f);
    putAll(t);
}

/**
 * 循环将Map中的元素,复制到新的HashTable中
 */
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()

HashTable的put()方法是往Hashtable中添加元素的方法

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();   //先将key进行hash运算,得到一个hash值
     //将hash值先进行位运算后,再与数组长度进行取模,得到索引index
     int index = (hash & 0x7FFFFFFF) % tab.length;
     @SuppressWarnings("unchecked")
     Entry<K,V> entry = (Entry<K,V>)tab[index];
     //判断key是否已经存在,如果存在,则用新的value替换旧的value,并返回旧的value;
     for(; entry != null ; entry = entry.next) {
         if ((entry.hash == hash) && entry.key.equals(key)) {
             V old = entry.value;
             entry.value = value;
             return old;
         }
     }
     
     //key不存在,将键值对插入Hashtable中,返回null
     addEntry(hash, key, value, index);
     return null;
 }

/**
 * 添加Entry
 */
private void addEntry(int hash, K key, V value, int index) {
    modCount++;  //数组的结构变化次数 +1

    Entry<?,?> tab[] = table;
    if (count >= threshold) {
        // Rehash the table if the threshold is exceeded
        //当数组上的元素个数已经达到阀值,则进行扩容操作
        rehash();  

        tab = table;
        hash = key.hashCode();  //重新计算hash值和index
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    // Creates the new entry.
    @SuppressWarnings("unchecked")
    //创建一个新Entry,并将Entry添加到数组指定的index位置
    Entry<K,V> e = (Entry<K,V>) tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;  //数组元素个数+1
}

/**
 1. 扩容,新数组长度 = 旧数组长度 * 2 + 1
 */
protected void rehash() {
     int oldCapacity = table.length; //记录旧数组的容量大小
     Entry<?,?>[] oldMap = table;

     // overflow-conscious code
     //计算新数组的容量大小 = 旧数组的容量大小 * 2 + 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<?,?>[] newMap = new Entry<?,?>[newCapacity];

     modCount++;
     //更新扩容阈值,为新数组大小的75%
     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;
             
             // 重新计算散列值 index
             int index = (e.hash & 0x7FFFFFFF) % newCapacity;

             // 将元素插入到新数组中 
             e.next = (Entry<K,V>)newMap[index];
             newMap[index] = e;
         }
     }
 }
remove()

HashTable的remove()方法用于从哈希表中删除指定键对应的元素,有两个删除方法

  1. 删除指定key
public synchronized V remove(Object key) {
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>)tab[index];
    for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) { //先匹配待删元素
            modCount++;
            if (prev != null) { //如果要删除的元素不是桶中第一个元素
                //next重新指向即可
                prev.next = e.next;
            } else {  //如果要删除的元素是桶中第一个元素
                tab[index] = e.next;
            }
            count--;
            V oldValue = e.value;
            e.value = null;
            return oldValue;  // 返回删除元素的值
        }
    }
    return null; // 没有找到要删除的元素,直接返回null
}
  1. 删除指定key和指定value
public synchronized boolean remove(Object key, Object value) {
     Objects.requireNonNull(value);  //校验指定value不能为null

     Entry<?,?> tab[] = table;
     int hash = key.hashCode(); //对指定key进行hash运算
     int index = (hash & 0x7FFFFFFF) % tab.length;  //再计算出数组的位置index
     @SuppressWarnings("unchecked")
     Entry<K,V> e = (Entry<K,V>)tab[index]; //获取数组上index位置出对应的元素
     for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
         //匹配待删元素
         if ((e.hash == hash) && e.key.equals(key) && e.value.equals(value)) {
             modCount++;
             if (prev != null) { //如果要删除的元素不是桶中第一个元素
                 //next重新指向即可
                 prev.next = e.next;
             } else { //如果要删除的元素是桶中第一个元素
                 tab[index] = e.next;
             }
             count--; //数组个数-1
             e.value = null; //设置待删元素的value为null
             return true;
         }
     }
     return false;  //未匹配到待删元素,返回false
 }
get()

HashTable的get()方法是用来获取给定键的值的方法。它可以根据传入的键值在哈希表中查找对应的节点,并返回该节点存储的值

public synchronized V get(Object key) {
    Entry<?,?> tab[] = table;
    int hash = key.hashCode(); //先将传入的键值通过哈希函数进行哈希计算,得到哈希值
    int index = (hash & 0x7FFFFFFF) % tab.length; //根据哈希值找到对应的桶
    //再在桶中查找对应的节点
    //使用for循环匹配,此处可能是链表结构
    //节点可能被放置在桶的链表中,需要遍历链表才能找到对应的节点
    //因此,在桶中查找节点的时间复杂度为O(1)~O(n),其中n为桶中节点的数量。
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            return (V)e.value;
        }
    }
    return null; //未匹配上节点,直接返回null
}
总结: HashTable的优点?
  1. 快速查找:Hashtable中的键值对是通过哈希函数计算索引值得到的,因此可以快速地查找元素,时间复杂度为 O(1)
  2. 高效插入和删除:由于哈希表的键值对是通过哈希函数计算索引值得到的,所以在插入或删除元素时,只需要计算一次哈希值即可定位元素,因此插入和删除操作都非常快速,时间复杂度为 O(1)
  3. 空间利用率高:哈希表只需要存储键和值,不需要额外的空间来存储指向下一个元素的指针,因此空间利用率比较高。
  4. 可扩展性:由于哈希表的实现是基于数组的,因此哈希表的大小可以动态地扩展或收缩,使其可以适应不同的数据集大小。
  5. 灵活性高:哈希表的键和值可以是任何类型的数据,因此可以适用于各种不同的数据类型和数据结构
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖界的小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值