实现一个自己的哈希表Hashtable(数组+TreeMap)

本文介绍了一种基于数组和TreeMap的哈希表实现,详细解析了hash值计算、容量调整策略及冲突解决机制。通过自定义的capacity数组,实现了动态的扩容和缩容,以保持良好的性能。

这个哈希表的实现是相关数据结构的学习记录,

 简单实现了一个哈希表,跟Java api中的Hashtable是不相同的,这个哈希表底层用的是数组和TreeMap,对于这个哈希表来说,有几个比较重要的地方,首先是hash值的运算方法,采用的是模除哈希表的长度M

   

     // 计算每个key对应的hash值
    private int hash(K key) {
        return (key.hashCode() & 0x7fffffff) % M;
    }

哈希表的容量,即M,这是一个避免哈希冲突的关键量,本类使用的是一个capacity数组(内部的数据是从相关网站上搜集的被测试可以有效避免hash冲突的素数),这个数组当中的每一个值在每次数组扩容跟缩容的时候使用。

然后是哈希表发生哈希冲突的平均负载量(哈希表中实际存储元素处于哈希表的长度),本类定义两个静态常量,upperTol与lowTol,当平均负载大于upperTol时进行扩容,在小于lowTol时进行缩容

其他需要注意的地方在代码的注释中有更详细的说明

import java.util.ArrayList;
import java.util.TreeMap;

public class HashTable<K, V> {
	// 如果哈希表的平均负载量达到upperTol,则要进行扩容
	private static final int upperTol = 10;
	// 如果哈希表的平均负载量小于lowTol,则要进行缩容
	private static final int lowTol = 2;
	// capacity数组中存的为每次扩容或者缩容的哈希表长度
	private final int[] capacity = { 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613,
			393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189,
			805306457, 1610612741 };
	// capacityIndex为指向数组元素的索引
	private int capacityIndex = 0;
	// 定义一个TreeMap类型的数组hashtable
	private TreeMap<K, V>[] hashtable;
	private int size;
	// M为哈希表的长度
	private int M;

	// hash表的初始化,有参构造,用户可自定义哈希表的长度
	public HashTable() {
		this.M = capacity[capacityIndex];
		size = 0;
		hashtable = new TreeMap[M];
		// 初始化哈希表中的每一个map
		for (int i = 0; i < M; i++) {
			hashtable[i] = new TreeMap<>();
		}

	}

	// 计算每个key对应的hash值
	private int hash(K key) {
		return (key.hashCode() & 0x7fffffff) % M;
	}

	public void add(K key, V value) {
		// 计算key的hash值,将此值对应的hash表中的map取出来
		TreeMap<K, V> map = hashtable[hash(key)];
		// 判断此map是否含有key对应值
		if (map.containsKey(key))
			// 如果包含直接添加
			map.put(key, value);
		else {
			// 否则,添加该值
			map.put(key, value);
			size++;
		}

		// 如果size/M>upperTol时进行扩容,这里将除法转化为乘法,避免了浮点类型的转换,
		// 扩容之前要对capacityIndex的合法性进行判断,以免发生溢出
		if (size > M * upperTol && capacityIndex + 1 < capacity.length)
			capacityIndex++;
		resize(capacity[capacityIndex]);
	}

	// 改变数组容量
	private void resize(int newM) {
		// TODO Auto-generated method stub

		TreeMap<K, V>[] newHashtable = new TreeMap[newM];
		// 初始化新数组中的每个map
		for (int i = 0; i < newM; i++) {
			newHashtable[i] = new TreeMap<>();
		}
		// 由于在下面有hash操作,模除的是新的M,则需要对M进行更新操作,同时,还要保留旧的M.
		int oldM = M;
		this.M = newM;
		// 循环遍历旧数组,取出原有的map
		for (int i = 0; i < oldM; i++) {
			TreeMap<K, V> map = hashtable[i];
			// 对于每一个map,取出其key的集合,将其中key对应的元素一个一个放入新的map中
			for (K key : map.keySet()) {
				newHashtable[hash(key)].put(key, map.get(key));
			}
		}
		// 将当前newHashtable指向hashtable
		this.hashtable = newHashtable;
	}

	public int Size() {
		return size;
	}

	public V remove(K key) {
		V ret = null;
		TreeMap<K, V> map = hashtable[hash(key)];
		if (map.containsKey(key)) {
			ret = map.remove(key);
			size--;
		}
		if (size < M * lowTol && capacityIndex - 1 > 0)
			capacityIndex--;
		resize(capacity[capacityIndex]);
		return ret;
	}

	public void set(K key, V value) {
		TreeMap<K, V> map = hashtable[hash(key)];
		if (!map.containsKey(key))
			throw new IllegalArgumentException("no such key");
		map.put(key, value);
	}

	public boolean contains(K key) {
		return hashtable[hash(key)].containsKey(key);
	}

	public V get(K key) {
		TreeMap<K, V> map = hashtable[hash(key)];
		V value = map.get(key);
		return value;
	}
}

       

 

<think>好的,我现在需要帮助用户解决如何用Java创建Hashtable或HashMap的示例代码的问题。首先,我得回忆一下Java中这两种数据结构的区别和使用方法。根据用户提供的引用内容,Hashtable和HashMap都是用来实现键值对的存储,但有一些关键差异,比如同步性、null值的支持以及性能上的不同。 用户可能需要示例代码来快速上手,所以应该分别给出Hashtable和HashMap的创建示例。同时,根据引用中的信息,Hashtable是线程安全的,但现在已经不推荐使用,而HashMap更常用,尤其是在不需要同步的情况下。此外,用户可能还需要知道如何添加元素、遍历元素等基本操作。 接下来,我需要检查引用中的具体内容,确保示例代码的正确性。例如,引用[4]提到HashMap支持null键和值,而Hashtable不支持。所以在示例代码中,需要避免在Hashtable中使用null,而在HashMap中可以展示这一点。 然后,考虑用户可能的后续问题,比如线程安全、性能优化或者与其他Map实现的区别。因此,在相关问题部分,应该包括这些方面,帮助用户进一步了解如何选择合适的数据结构。 还需要注意代码格式的正确性,使用正确的Java语法,比如泛型、初始化方法等。同时,根据用户提供的引用,Hashtable使用Enumeration遍历,而HashMap使用Iterator,这点在代码示例中要体现出来,比如展示两种遍历方式。 最后,确保回答结构清晰,分步骤说明,并且引用相关的资料来支持回答,比如提到ConcurrentHashMap作为替代方案时,引用[5]中的建议。</think>### Java中创建Hashtable与HashMap的示例代码 #### 1. Hashtable示例 Hashtable是线程安全的哈希表,但因其同步特性可能导致性能问题,现多被ConcurrentHashMap替代[^4]。**不支持null键和值**,使用Enumeration或Iterator遍历。 ```java import java.util.Hashtable; import java.util.Enumeration; public class HashtableExample { public static void main(String[] args) { Hashtable<Integer, String> hashtable = new Hashtable<>(); // 添加元素(不可用null) hashtable.put(1, "Java"); hashtable.put(2, "Python"); // 通过Enumeration遍历 Enumeration<Integer> keys = hashtable.keys(); while (keys.hasMoreElements()) { Integer key = keys.nextElement(); System.out.println("Key: " + key + ", Value: " + hashtable.get(key)); } } } ``` #### 2. HashMap示例 HashMap非线程安全但性能更优,**支持null键和值**,广泛用于单线程环境。Java 8后通过树化优化哈希冲突性能[^2]。 ```java import java.util.HashMap; import java.util.Map; public class HashMapExample { public static void main(String[] args) { HashMap<String, Integer> hashMap = new HashMap<>(16, 0.75f); // 添加元素(允许null) hashMap.put("Apple", 10); hashMap.put("Banana", null); hashMap.put(null, 5); // 通过EntrySet遍历(推荐方式) for (Map.Entry<String, Integer> entry : hashMap.entrySet()) { System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); } } } ``` --- ### 关键区别总结 | 特性 | Hashtable | HashMap | |---------------------|--------------------------------|---------------------------------| | **线程安全** | 是(同步方法) | 否(需外部同步) | | **Null支持** | 键/值均不可为null | 键/值均可为null[^4] | | **遍历方式** | Enumeration或Iterator[^3] | Iterator[^3] | | **初始容量定义** | 默认11,自动扩容×2+1 | 默认16,自动扩容×2 | | **哈希冲突处理** | 链表 | 链表+红黑树(Java 8树化) | --- ### 选择建议 - 需要线程安全时优先选`ConcurrentHashMap`而非Hashtable[^5] - 高频读写场景用HashMap,需排序时用TreeMap[^4] - 指定初始容量和负载因子优化性能(如`new HashMap<>(32, 0.5f)`) ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值