`
Liang_Hong
  • 浏览: 6713 次
社区版块
存档分类
最新评论

HashTable的Java实现及性能对比

 
阅读更多

1、HashTable的概述

   从基本层面讲,数据结构有数组与链表两种。数组具有查找快,但插入耗时的特点;链表具有插入快,但查找费时的特点。有没有可能在查找与插入之间取得平衡呢?哈希表的诞生回答了这个问题。

   构建一个好的哈希表,主要得考量哈希函数解决冲突这两面。不管是哈希函数,还是解决冲突,往深里挖都是可以走得很远很远。

 

2、实现哈希表的思路

    直接上思维导图,思维上建立了,写个哈希表就不那么费力了。



 

 

public class HashTable<K, V> {
	private int size;//元素个数
	private static int initialCapacity=16;//HashTable的初始容量
	private Entry<K,V> table[];//实际存储数据的数组对象
	private static float loadFactor=0.75f;//加载因子
	private int threshold;//阀值,能存的最大的数max=initialCapacity*loadFactor
	
	//构造指定容量和加载因子的构造器
	public HashTable(int initialCapacity,float loadFactor){
		if(initialCapacity<0)
			throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity);
		if(loadFactor<=0)
			throw new IllegalArgumentException("Illegal loadFactor:"+loadFactor);
		this.loadFactor=loadFactor;
		threshold=(int)(initialCapacity*loadFactor);
		table=new Entry[threshold];
	}
	
	//使用默认参数的构造器
	public HashTable(){
		this(initialCapacity,loadFactor);
	}
	
	//放入元素
	public boolean put(K key,V value){
		//取得在数组中的索引值
		int hash=key.hashCode();
		Entry<K,V> temp=new Entry(key,value,hash);
		if(addEntry(temp,table)){
			size++;
			return true;
		}
		return false;
	}
	
	//添加元素到指定索引处
	private boolean addEntry(HashTable<K, V>.Entry<K, V> temp,
			HashTable<K, V>.Entry<K, V>[] table) {
		//1.取得索引值
		int index=indexFor(temp.hash,table.length);
		//2.根据索引找到该位置的元素
		Entry<K,V> entry=table[index];
		//2.1非空,则遍历并进行比较
		if(entry!=null){
			while(entry!=null){
				if((temp.key==entry.key||temp.key.equals(entry.key))&&temp.hash==entry.hash
						&&(temp.value==entry.value||temp.value.equals(entry.value)))
						return false;
				else if(temp.key!=entry.key&&temp.value!=entry.value){
					if(entry.next==null)
						break;
					entry=entry.next;
				}
			}
			//2.2链接在该索引位置处最后一个元素上
			addEntryLast(temp,entry);
		}
		//3.若空则直接放在该位置
		setFirstEntry(temp,index,table);
		//4.插入成功,返回true
		return true;
	}
	
	//链接元素到指定索引处最后一个元素上
	private void addEntryLast(HashTable<K, V>.Entry<K, V> temp,
			HashTable<K, V>.Entry<K, V> entry) {
		if(size>threshold)
			reSize(table.length*4);
		entry.next=temp;
	}

	//初始化索引处的元素值
	private void setFirstEntry(HashTable<K, V>.Entry<K, V> temp, int index,
			HashTable<K, V>.Entry<K, V>[] table) {
		if(size>threshold)
			reSize(table.length*4);
		table[index]=temp;
		//注意指定其next元素,防止多次使用该哈希表时造成冲突
		temp.next=null;
	}

	//扩容容量
	private void reSize(int newSize) {
		Entry<K,V> newTable[]=new Entry[newSize];
		threshold=(int) (loadFactor*newSize);
		for(int i=0;i<table.length;i++){
			Entry<K,V> entry=table[i];
			//数组中,实际上每个元素都是一个链表,所以要遍历添加
			while(entry!=entry){
				addEntry(entry,newTable);
				entry=entry.next;
			}
		}
		table=newTable;
	}


	//计算索引值
	private int indexFor(int hash, int tableLength) {
		//通过逻辑与运算,得到一个比tableLength小的值
		return hash&(tableLength-1);
	}
	
	//取得与key对应的value值
	protected V get(K k){
		Entry<K,V> entry;
		int hash=k.hashCode();
		int index=indexFor(hash,table.length);
		entry=table[index];
		if(entry==null)
			return null;
		while(entry!=null){
			if(entry.key==k||entry.key.equals(k))
				return entry.value;
			entry=entry.next;
		}
		return null;
	}
	
	//内部类,包装需要存在哈希表中的元素
	class Entry<K,V>{
		Entry<K,V> next;
		K key;
		V value;
		int hash;
		
		Entry(K k,V v,int hash){
			this.key=k;
			this.value=v;
			this.hash=hash;
		}
	}
	
}

 

 注:(1)本代码中采用的hash算法是:第一步采用JDK给出的hashcode()方法,计算加入对象的一个哈希值,其中hashcode()在Object类中定义为:public native int hashcode();说明这是一个本地方法,它的具体实现跟本地机器相关。第二步是通过hashcode&(table.lenth-1),返回一个比length小的值,即为索引值。
        (2)该代码解决冲突的办法是采用的“挂链法”。
        (3)代码中的加载因子loadFactor是参见HashMap的源码,据说0.75是一个耗时与占用内存的折中值。
        (4)插入元素时,若索引处位置非空,要与已有元素进行对比,根据java规范,并不强制不相等的两个对象拥有不相等的hashcode值,因此还需进一步调用equals方法进行判断。
 
3、与数组与链表进行性能对比
     数组直接采用的java.util.ArrayList;
     链表直接采用的java.util.LinkedList;
     代码如下:
public class Main {
	
	public static ArrayList<User> al;
	public static LinkedList<User> ll;

	public static void main(String[] args) {
		Main m = new Main();
		
		al = new ArrayList<User>();
		ll = new LinkedList<User>();
		
		m.insert();
		m.find(9999);
		
	}
	
	public void insert(){
		long l;
		//测试数组队列插入时间
		l = System.currentTimeMillis();
		for(int i=0; i<1000000; i++){
			User u = new User(i,"abc"+i);
			al.add(u);
		}
		l = System.currentTimeMillis() - l;
		System.out.println("ArrayList插入(用时):"+l);
		
	    //测试链表插入时间
		l = System.currentTimeMillis();
		for(int i=0; i<1000000; i++){
			User u = new User(i,"abc"+i);
			ll.add(u);
		}
		l = System.currentTimeMillis()-l;
		System.out.println("LinkedList插入(用时):"+l);
		
	}
	
	public void find(int id){
		long l;
		
		//测试数组队列查找时间
		l = System.currentTimeMillis();
		for(int i=0; i<1000000; i++){
			if(al.get(i).getId() == id){
				System.out.println("find it!");
				l = System.currentTimeMillis()-l;
				System.out.println("ArrayList查找时间:"+l);
				break;
			}
		}

		//测试链表查找时间
		l = System.currentTimeMillis();
		for(int i=0; i<1000000; i++){
			if(ll.get(i).getId() == id){
				System.out.println("find it!");
				l = System.currentTimeMillis()-l;
				System.out.println("LinkedList查找时间:"+l);
				break;
			}
		}	
	}
	
	//测试用的User类
	class User{
		private int id;
		private String name;
		
		public User(int id, String name){
			this.id = id;
			this.name = name;
		}
		
		public int getId(){
			return id;
		}
	}
}
 运行结果:

 现在来看看哈希表:
public class Manager {

	public static void main(String[] args) {
		HashTable<String,String> ht=new HashTable<String,String>();
		long beginTime=System.currentTimeMillis();
		for(int i=0;i<1000000;i++){
			ht.put(i+"", "test"+i);
		}
		long endTime=System.currentTimeMillis();
		System.out.println("The HashTable insert time is:"+(endTime-beginTime));
		
		long beginTime2=System.currentTimeMillis();
		ht.get(9999+"");
		long endTime2=System.currentTimeMillis();
		System.out.println("The HashTable Search time is:"+(endTime2-beginTime2));
	}

}
 运行结果:

 注:同是插入1000000数量级的元素,同是查找第9999个元素。其时间效率对比应该是很明显的。
 
4、总结及感言
     不弄懂散列表怎敢说自己是学习了数据结构的?
上学期的债今天终于偿还了。这一学习过程,深深体会到散列表这一数组与链表的综合体,的确是非常奇妙的。当然哈希表只是一个开始,是数组与链表的一个进阶。后面更博大精深的是树与图。
这一个过程中,首先是找到了两篇很有质量的博客,对于我理解散列表起到了关键作用,时常感慨互联网的伟大,就是每个互联网的参与者都贡献出自己的智慧,薪火相传,终究会酝酿出更大的智慧。
另一个就是查看源代码,才真的感受到代码之美,源代码聚集了无数程序员的智慧,其精炼,其逻辑的严密性,是不看不知道,一看就好中意。看来要持续进阶学习,源代码算是一条康庄大道。
 
参考资料:
JDK API Hashtable Hashtable源代码  
  • 大小: 18.4 KB
  • 大小: 23.4 KB
  • 大小: 4 KB
  • 大小: 1.7 KB
分享到:
评论

相关推荐

    dotnet C# 字典 Dictionary 和 Hashtable 的性能对比.rar

    在.NET框架中,`Dictionary, TValue&gt;`和`Hashtable`都是常见的哈希表实现,用于存储...通过阅读“dotnet C# 字典 Dictionary 和 Hashtable 的性能对比.md”文档,你可以进一步了解它们在实际应用中的表现和测试结果。

    Java容器类List、ArrayList、Vector及map、HashTable应用

    List、ArrayList、Vector及map、HashTable是Java中常用的容器类,它们都继承自Collection接口,并提供了不同的实现方式和特点。在实际开发中,选择合适的容器类是非常重要的。 Collection接口是Java中最基本的集合...

    java的hashtable的用法.pdf

    在Java编程语言中,`Hashtable`是`Dictionary`类的一个具体实现,它是早期Java版本中的一个线程安全的键值对存储容器。`Hashtable`遵循了一些基本的映射概念,如存储键值对、根据键查找值以及添加、删除键值对等。...

    hashMap和hashTable的区别

    `HashMap` 和 `HashTable` 都是 Java 集合框架中非常重要的数据结构,它们都实现了 `Map` 接口,用于存储键值对。尽管它们在功能上有很多相似之处,但在实现细节和性能特性上存在显著差异。 #### 二、主要区别 1. ...

    哈希表java实现及简单演示

    标签中的`hashtable`可能指的是`java.util.Hashtable`类,这是Java早期提供的一种线程安全的哈希表实现。虽然`HashMap`在性能上通常优于`Hashtable`,但`Hashtable`在多线程环境中更安全,因为它的所有方法都是同步...

    Java性能优化比较

    Java性能优化是提升Java应用程序效率的关键技术,涵盖了代码优化、内存管理、并发处理等多个方面。在Java开发过程中,了解并掌握这些优化技巧可以显著提高应用的响应速度和资源利用率。 首先,我们关注的是代码层面...

    java的hashtable的用法.docx

    在Java编程语言中,`Hashtable`是`Dictionary`类的一个具体实现,它提供了一个基于键值对的数据结构,允许程序员存储和检索对象。`Hashtable`类是线程安全的,因此可以在多线程环境中直接使用,而无需额外的同步控制...

    HashMap与HashTable的区别(含源码分析)

    在Java编程语言中,`HashMap`和`HashTable`都是实现键值对存储的数据结构,但它们之间存在一些显著的区别,这些区别主要体现在线程安全性、性能、null值处理以及一些方法特性上。以下是对这两个类的详细分析: 1. ...

    Java哈希表性能优化

    Java标准库中的哈希表实现主要依赖于`java.util.Hashtable`类。在创建`Hashtable`对象时,用户可以通过构造函数指定初始容量`C`以及装载因子`F`。一旦对象被创建,装载因子便无法改变。装载因子决定了哈希表的负载...

    HashMap和HashTable的区别和不同

    性能对比 - **HashMap**通常比**HashTable**具有更好的性能。原因包括: - `HashMap`的非线程安全设计减少了同步开销。 - `HashMap`在哈希计算和扩容策略上的优化使其在大多数情况下都能提供更高的性能。 - ...

    hashmap与hashtable区别

    历史背景及实现原理 - **Hashtable**:该类继承自`Dictionary`类,并且实现了`Map`接口。它最早出现在Java 1.0版本中,可以视为早期实现键值对存储的一种方式。 - **HashMap**:相比之下,`HashMap`是在Java 1.2...

    并行环境下Java哈希机制的对比及重构.pdf

    本研究针对这一问题进行了深入探讨,并着重分析了在并行程序中,这两种数据结构的性能对比以及如何重构相关代码,以提高并行程序的运行效率和稳定性。 首先,要了解Hashtable与ConcurrentHashMap的基本区别。...

    Java中List、ArrayList、Vector及map、HashTable、HashMap分别的区别.

    LinkedList是List接口的另一个实现,它基于双向链表实现,对于在列表中间插入和删除元素,LinkedList的性能优于ArrayList,因为不需要移动元素。但在随机访问元素时,LinkedList的性能较差,因为需要从头或尾部开始...

    java中HashMap,LinkedHashMap,TreeMap,HashTable的区别

    ### Java中HashMap, LinkedHashMap, TreeMap,HashTable的区别 在Java编程语言中,`Map`接口是集合框架中的一个重要组成部分,用于存储键值对。本文将详细分析四种常用的`Map`实现类:`HashMap`, `LinkedHashMap`, ...

    在Java中运用Hashtable.doc

    Java提供了`java.util.Hashtable`和`java.util.HashMap`两个类来实现哈希表的功能。这两个类非常相似,通常提供相同的公共接口,但在某些方面有所不同。 - **Hashtable** 是线程安全的,这意味着可以在多线程环境中...

    Hashtable和HashMap的区别:

    #### 四、性能比较 - **Hashtable**:由于其同步特性,`Hashtable` 的性能通常会低于 `HashMap`。在单线程环境下,这种差距尤为明显。 - **HashMap**:在不考虑同步的情况下,`HashMap` 提供了更好的性能表现,...

    java接口API,LIST,HASHTABLE

    `HashTable`是古老的键值对存储结构,线程安全但效率较低,已被`HashMap`所取代,`HashMap`是非线程安全但性能更高的选择。 集合框架的使用极大地提高了代码的可读性和复用性,同时通过接口的定义,可以灵活地更换...

    java系统性能优化手册

    ### Java系统性能优化手册知识点详解 #### 一、前言 在Java系统开发过程中,为了提升系统的整体性能,开发者需要掌握一系列的优化技巧。本文档旨在通过总结《河南省人口与计划生育利益导向管理信息系统》的实际应用...

    链地址法java代码

    3. **`HashTable.java`**:这个文件可能包含了整个哈希表的实现,包括哈希函数、链表结构以及相关操作如插入、删除和查找等。哈希表通常包含一个数组,每个元素是一个链表,数组的大小通常是质数,以减少哈希冲突。 ...

    hashtable存储IP

    实际应用中,我们可以根据具体需求进一步优化,例如使用更现代的`HashMap`替代`Hashtable`(因为`Hashtable`是同步的,可能在多线程场景下性能不佳),或者考虑使用`ConcurrentHashMap`以实现线程安全且高效的并发...

Global site tag (gtag.js) - Google Analytics