Java集合-----Hashtable

本文介绍了Java中的Hashtable类,它与HashMap相似但有区别。Hashtable继承Dictionary类,不允许键值对为null,且是线程安全的。文章详细讲解了Hashtable的属性、put()和get()方法,以及其线程安全的实现。还对比了Hashtable与HashMap在继承关系、存储键值对的策略和线程安全性上的差异。

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

概述

      Hashtable,很大程度上和HasMap的实现差不多,但也有不同,之后我们会阐述。Hashtable和HashMap都可以将key和value结合起来构成键值对,通过put(key,value)方法保存起来,然后通过get(key)方法获取相应的value。

Hashtable类定义

      Hastable在Java中定义:

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

      我们发现,去HashMap不同的是,Hashtable继承于Dictionary类,这个类是将任何键映射到相应值的类的抽象弗雷。每个键和每个值都是一个对象。在任何一个Dictionary对象中,每个键至多与一个值相关联。

Hashtable中的属性

      我们来看看Hashtable的类属性

	// Entry[]数组:用于存放集合中的元素
	private transient Entry<?,?>[] table;

	// 集合中Entry键值对的数量。注意count不是容器的大小。
    private transient int count;
	
	// 阈值
    private int threshold;

	// 加载因子
    private float loadFactor;

	//记录集合修改的次数。
    private transient int modCount = 0;

    private static final long serialVersionUID = 1421746759512286392L;

      类构造器:

// 默认构造器,默认创建一个容量为11的Entry[]数组,且加载因子为0.75
public Hashtable() {
        this(11, 0.75f);
}

// 带有一个参数的构造器,其中调用的是带有两个参数的构造器。
public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
}


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);
		// initalCapacity为0时,用 1 赋值
        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        // 创建容器大小。
        table = new Entry<?,?>[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}

// 构造一个与给定的 Map 具有相同映射关系的新哈希表。
public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
}

主要方法

      put()方法:将指定key映射到此哈希表中的指定value。Hashtable中的key和value不能为空。

 public synchronized V put(K key, V value) {
        // 确保value不为空
        if (value == null) {
            throw new NullPointerException();
        }

        Entry<?,?> tab[] = table;
        int hash = key.hashCode(); //计算哈希值
        int index = (hash & 0x7FFFFFFF) % tab.length; //计算该key在table中的索引位置
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];//拿到这个索引下的结点
        // for循环主要是遍历该位置下的所有结点是否有和该key相同的
        for(; entry != null ; entry = entry.next) {
        // 如果有,则新值覆盖旧值
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
		// 如果没有,则在该位置上,通过拉链法添加该key-value键值对。
        addEntry(hash, key, value, index);
        return null;
    }

      Hashtable在put()方法中,调用了addEntry()方法添加一个新的键值对,我们来看一看这个方法:

private void addEntry(int hash, K key, V value, int index) {
// 对集合的修改次数+1
        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;
        }

        // 添加一个新的键值对。        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

      我们再来看一看扩容:rehash():

    @SuppressWarnings("unchecked")
    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;
        }
        // new一个新的数组。
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
        // 计算阈值。
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;
		
		// 计算原数组中每个index位置上的结点在新数组中的位置。
        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;

		// 这里使用e.hsah && 0x7FFFFFFF目的是为了尽可能保持与原数组的顺序相同。
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

      get():

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

      通过计算出key的hash值进而计算出在数组中的index位置,然后遍历这个index上的所有结点,找到了返回该key对应的value值,没有找到返回null。

总结

      Hashtable中存储键值对采用的是 “链地址法” ,且Hashtable是线程安全的。我们发现,Hashtable中的方法都被synchronized修饰,都被加了一把锁,所以,在多线程环境下,Hashtable是线程安全的。

Hashtable和HashMap的区别:
      ① Hashtable继承Dictionary类,而HashMap继承AbstractMap类。Dictionary类是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的实现,以最大限度地减少实现Map接口所需要的工作。

      ② HashMap可以存储键为null,值为null的键值对,但是HashTable的key和value都不允许为null。当Hashtable的value为null时,为抛出异常。

        if (value == null) {
            throw new NullPointerException();
        }

      ③ HashMap是线程不安全的,Hashtable是线程安全的, Hashtable中的方法使用synchronized修饰,在多线程环境下线程安全,但效率不高。Collections中提供了一个synchronizedMap()方法,可以创建一个线程安全的Map对象,所以如果我们要使用HashMap并要保证线程安全的话,我们可以调用Collections中的synchronizedMap()方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值