Java集合系列(7)--HashTable

本文详细介绍了HashTable的基本概念、数据结构、常用API及其使用示例。重点对比了HashTable与HashMap的区别,包括线程安全性、允许null值等方面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面我们学习了HashMap,现在我们来学习HashTable。和之前一样,相对HashTable有个整体的认识,然后学习它的源码,最后通过代码实例学会它。
一、HashTable基本概述

和HashMap一样,HashTable底层也是一个散列表(链表+数组),存储的内容为键值对(Key-Value)映射。
HashTable继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。注意HashMap是继承AbstractMap类。
HashTable是同步的,意味着它是线程安全的。它的Key、Value值不允许为null;并且,HashTable存储的元素也不是有序的。
面试易考点:

关注点结论
结合底层实现的数据结构散列表(数组+列表)
集合中元素是否允许为空Key允许为空,value不许为空
是否允许数据重复key值会被覆盖,value允许重复
是否有序无序,是指不是按put进来的顺序
是否线程安全线程安全(是同步synchronized的)
二、HashTable的数据结构

1>基本特性
HashTable的实例有两个参数影响其性能:初始容量和加载因子。容量是指哈希表中桶的数量。初始容量就是哈希表初始化时的容量。注意。哈希表的状态为open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子是对哈希表在其容量自增得到的一个程度的描述。初始容量和加载因子这两个参数只是对该实现的提示。至于何时以及是否需要调用rehash方法,则依赖于具体实现。
一般,加载因子默认是0.75,这是在时间和空间复杂度上的一种最优选择。加载因子过高虽然减少了空间开销,但同时也增加了查找的时间(在大多数HashTable的实现中,包括get和put操作,都体现了这一点)
以上描述的与HashMap大致相同。
2>HashTable与Map关系如下图:
image

由上面的关系图得到:
a. HashTable继承于Dictionary类(注意与HashMap的不同),实现了Map接口。Map是Key-Value键值对接口,Dictionary是声明了操作键值对的函数接口的抽象类。
b. HashTable是通过”拉链法”实现的哈希表。它包括几个重要的成员变量:table, count, threshold, loadFactor, modCount。
table:是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的”key-value键值对”都是存储在Entry数组中的;
count:是Hashtable的大小,它是Hashtable保存的键值对的数量;
threshold:是Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值=”容量*加载因子”;
loadFactor:加载因子;
modCount:是用来实现fail-fast机制的.
3>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);
    //当initialapacity输入为0时,默认设置成1
    if (initialCapacity==0)
        initialCapacity = 1;
    this.loadFactor = loadFactor;
    //这里就是和hashMap不同的地方,hashMap是延迟加载,在第一次put的时候才new对象,HashTable这里是在构造器中直接new了。
    table = new Entry[initialCapacity];
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    initHashSeedAsNeeded(initialCapacity);
}

//指定容量大小的构造函数
public HashTable(int initialCapacity) {
          this(initialCapacity, 0.75f);
 }

 //默认构造函数
 public Hashtable(){
     this(11,0.75f);
 }

 //包含子Map的构造函数
 public HashTable(Map<? extends K,? extends V> t){
     this(Math.max(2*t.size(),11),0.75f);
     putAll(t);
 }
三、HashTable常用API

其实常用方法和前一章的内容差不多,所以参照前面的内容进行分析
1>clear():
用于清空HashTable。它是通过将所有的元素设为null来实现的。源码如下:

public synchronized void clear() {
    Entry tab[] = table;
    modCount++;
    for (int index = tab.length; --index >= 0; )
        tab[index] = null;
    count = 0;
}

2>containsKey()
用于判断HashTable是否包含key。源码如下:

public synchronized boolean containsKey(Object key) {
    Entry tab[] = table;
    int hash = key.hashCode();
    // 计算索引值,
    // % tab.length 的目的是防止数据越界
    int index = (hash & 0x7FFFFFFF) % tab.length;
    // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素
    for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            return true;
        }
    }
    return false;
}

3>containsValue()和containsValue()
都是用于判断HashTable是否包含“值为value”的元素,源码如下:

public boolean containsValue(Object value) {
    return contains(value);
}

public synchronized boolean contains(Object value) {
    // Hashtable中“键值对”的value不能是null,
    // 若是null的话,抛出异常!
    if (value == null) {
        throw new NullPointerException();
    }

    // 从后向前遍历table数组中的元素(Entry)
    // 对于每个Entry(单向链表),逐个遍历,判断节点的值是否等于value
    Entry tab[] = table;
    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;
}

4>elements()
用于返回“所有value”的枚举对象

public synchronized Enumeration<V> elements() {
    return this.<V>getEnumeration(VALUES);
}

// 获取Hashtable的枚举类对象
private <T> Enumeration<T> getEnumeration(int type) {
    if (count == 0) {
        return (Enumeration<T>)emptyEnumerator;
    } else {
        return new Enumerator<T>(type, false);
    }
}

从上面代码中,我们可以得到:
若Hashtable的实际大小为0,则返回空枚举类对象emptyEnumerator;否则返回正常的Enumerator对象。

5>get(object key)
用于获取key对应的value,若没找到则返回空。

public synchronized V get(Object key) {
    Entry tab[] = table;
    int hash = key.hashCode();
    // 计算索引值,
    int index = (hash & 0x7FFFFFFF) % tab.length;
    // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素
    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;
}

6>put(K key,V value)
用于对外提供接口,让Hashtable对象可以通过put()将Key-Value键值对添加到Hashtable中,代码如下:

public synchronized V put(K key, V value) {
    // Hashtable中不能插入value为null的元素!!!
    if (value == null) {
        throw new NullPointerException();
    }

    // 若“Hashtable中已存在键为key的键值对”,
    // 则用“新的value”替换“旧的value”
    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;
            }
    }

    // 若“HashTable中不存在键为key的键值对”,
    // (1) 将“修改统计数”+1
    modCount++;
    // (2) 若“HashTable实际容量” > “阈值”(阈值=总的容量 * 加载因子)
    //  则调整HashTable的大小
    if (count >= threshold) {
        // Rehash the table if the threshold is exceeded
        rehash();

        tab = table;
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    // (3) 将“Hashtable中index”位置的Entry(链表)保存到e中
    Entry<K,V> e = tab[index];
    // (4) 创建“新的Entry节点”,并将“新的Entry”插入“Hashtable的index位置”,并设置e为“新的Entry”的下一个元素(即“新Entry”为链表表头)。        
    tab[index] = new Entry<K,V>(hash, key, value, e);
    // (5) 将“Hashtable的实际容量”+1
    count++;
    return null;
}

7>putAll()
用于将已有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());
 }

8>remove(object key)
用于删除Hashtable中键值为key的元素

public synchronized V remove(Object key) {
    Entry tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    // 找到“key对应的Entry(链表)”
    // 然后在链表中找出要删除的节点,并删除该节点。
    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;
}
四、HashTable遍历方式

1>遍历HashTable的键值对

// 假设table是Hashtable对象
// table中的key是String类型,value是Integer类型
Integer integ = null;
Iterator iter = table.entrySet().iterator();
while(iter.hasNext()) {
    Map.Entry entry = (Map.Entry)iter.next();
    // 获取key
    key = (String)entry.getKey();
        // 获取value
    integ = (Integer)entry.getValue();
}

2>通过Iterator遍历HashTable键值

// 假设table是Hashtable对象
// table中的key是String类型,value是Integer类型
String key = null;
Integer integ = null;
Iterator iter = table.keySet().iterator();
while (iter.hasNext()) {
        // 获取key
    key = (String)iter.next();
        // 根据key,获取value
    integ = (Integer)table.get(key);
}

3>通过Iterator遍历HashTable的值

// 假设table是HashTable对象
// table中的key是String类型,value是Integer类型
Integer value = null;
Collection c = table.values();
Iterator iter= c.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}

4>通过Enumeration遍历HashTable的键

Enumeration enu = table.keys();
while(enu.hasMoreElements()) {
    System.out.println(enu.nextElement());
}   

5>通过Enumeration遍历Hashtable的值

Enumeration enu = table.elements();
while(enu.hasMoreElements()) {
    System.out.println(enu.nextElement());
}

从上面的代码可以看出,涉及到最多的是entrySet()、keySet()、value()、keys()、elements()等方法。
entrySet():获取Hashtable的“键值对”的Set集合;
keySet():获取Hashtable的“键”的Set集合;
value():获取Hashtable的“值”的集合;
keys():获取Hashtable的集合;
elements():获取Hashtable的集合。

五、HashTable代码示例
package Test;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;

/**
 * Created by LKL on 2017/2/18.
 * Hashtable的测试程序。
 */
public class TestHashTable {
        public static void main(String[] args) {
            testHashtableAPIs();
        }
        private static void testHashtableAPIs() {
            // 初始化随机种子
            Random r = new Random();
            // 新建Hashtable
            Hashtable table = new Hashtable();
            // 添加操作
            table.put("one", r.nextInt(10));
            table.put("two", r.nextInt(10));
            table.put("three", r.nextInt(10));
            // 打印出table
            System.out.println("table:"+table );

            // 通过Iterator遍历key-value
            Iterator iter = table.entrySet().iterator();
            while(iter.hasNext()) {
                Map.Entry entry = (Map.Entry)iter.next();
                System.out.println("next : "+ entry.getKey() +" - "+entry.getValue());
            }

            // Hashtable的键值对个数
            System.out.println("size:"+table.size());

            // containsKey(Object key) :是否包含键key
            System.out.println("contains key two : "+table.containsKey("two"));
            System.out.println("contains key five : "+table.containsKey("five"));

            // containsValue(Object value) :是否包含值value
            System.out.println("contains value 0 : "+table.containsValue(new Integer(0)));

            // remove(Object key) : 删除键key对应的键值对
            table.remove("three");

            System.out.println("table:"+table );

            // clear() : 清空Hashtable
            table.clear();

            // isEmpty() : Hashtable是否为空
            System.out.println((table.isEmpty()?"table is empty":"table is not empty") );
        }


}

运行结果如下:

table:{two=3, one=8, three=2}
next : two - 3
next : one - 8
next : three - 2
size:3
contains key two : true
contains key five : false
contains value 0 : false
table:{two=3, one=8}
table is empty

文章只是作为自己的学习笔记,借鉴了网上的许多案例,如果觉得阔以的话,希望多交流,在此
谢过…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值