`

哈希表详解示例

阅读更多

Hash表实现的意义

作为数据类型的一种,Hash表做到了数组和链表两类基本数据类型的完美结合。Hash表继承两者的优点,让我们在做数据处理方面有了很多的方便,不但是时间上的还有空间上的。在数组中我们知道,存放的数据与及记录的关键字上没有太多的关系,很多都只是一个随机的关系。那么这样在我们进行相应的操作时,会大大的减少我们的工作效率,主要是因为找数据时会进行一系列的比较,这样消耗的比较的次数太多时花费的时间就会变多,自然而然效率就这样被削弱了。所以最最理想的情况就是我们能直接通过他们建立的关系,直接的找到结构中的位置来获取数据。

关系怎么建立呢

现在我们的主要任务就是找到方法来建立对应的关系。这里的方法其实有很多种。不同的数据采用的方法不同。主要有数据分析法、平均取中法、取余法、随机数等等方法。在这里不做具体的介绍。所以今天我在自定义自己的Hash表时,用的是JDK中提供的hashCode()方法。有兴趣的还可以相应的拓展。

 

Hash表到底是什么

真的是真的,听了我说上面的你也许还没有理解Hash表是个什么东东。一句话:就是以链表为元素的数组对象。在我们将数据放在这个所谓的数组对象时,有两种情况:1:原来数组在该下标已经有元素存在了,那么要放入的节点如果经过hashCode()及indexFor()方法计算过后他的下标也是一样的,那么我们采用挂链法将这个节点放到最后;2:如果没有,那么放到对应的index下标中,作为根节点。

 

最清楚的Hash表代码解释

说了这些肯定也不会马上理解,没事,我们来看代码。

为了大家能够看的清楚明了,这里我建立三个类:一个是链表类、一个是Hash表、一个是测试类

里面的方法都有详细的介绍,主要的是把思路理清。

链表Entry类:

package 哈希表1029;
/**
 * 链表
 * @author 祝侦科
 *
 */
public class Entry<K,V> {
	Entry<K,V> next;
	K key;
	V value;
	int hash;
	
	public Entry(K key,V value,int hash){
		this.key = key;
		this.value = value;
		this.hash = hash;
	}
	
}

 哈希表类:

package 哈希表1029;
/**
 * 自定义的哈希表
 * @author 祝侦科
 *
 */
public class Hash<K,V> {
	private  int size;//链表数组的大小
	private static int INIT_CAPACITY = 16;//默认的容量,即数组的大小
	private static float LOAD_FACTOR = 0.75f;//默认的装载因子
	private int max;//能存的最大的数
	private Entry<K,V>[] entryArray;//链表数组
	
	public Hash(int capacity,float factor){
		this.INIT_CAPACITY = capacity;
		max = (int)(capacity*factor);
		entryArray = new Entry[capacity];
	}
	
	public Hash(){
		this(INIT_CAPACITY, LOAD_FACTOR);
	}
	
	/**
	 * 根据hashcode及数组的容量来计算出该哈希码的下标
	 * @param hashcode  hash值
	 * @param arraySize  数组长度
	 * @return 下标
	 */
	public int indexFor(int hashcode,int arraySize){
		return hashcode & (arraySize-1);
	}
	
	/**
	 * 扩容以减少冲突阀值
	 * @param arraySize:扩容后新数组的容量
	 */
	public void reHash(int arraySize){
		//创建扩容后的数组
		Entry<K,V>[] newArray = new Entry[arraySize];
		max = (int)(arraySize*LOAD_FACTOR);
		for(int i=0;i<entryArray.length;i++){
			//取得每一个链表对象
			Entry<K,V> entry = entryArray[i];
			while( null != entry){//遍历链表的每个节点,并且将节点加到新的数组中
				this.setEntry(entry, newArray);
				entry = entry.next;
			}
		}
		entryArray = newArray;//将新的数组赋给原来的数组
	}
	
	/**
	 * 将节点放到链表数组中
	 * @param temp:要放入的节点
	 * @param entryArray:放到这个数组中
	 * @return:没有完全相同的则返回true,否则false
	 */
	public boolean setEntry(Entry<K,V> temp,Entry[] entryArray){
		int index = this.indexFor(temp.hashCode(), entryArray.length);//获取要放入的节点的下标
		Entry<K,V> entry = entryArray[index];//得到对应的链表对象
		if(entry != null){//在while循环中遍历这个链表
			while(null != entry){//遍历整个链表是为了发现是否有相同的节点
			if((temp.key == entry.key || temp.key.equals(entry.key))
					&&(temp.value == entry.value || temp.value.equals(entry.value))
					&& temp.hash == entry.hash){
				return false;//这里要注意比较的方法。。要两种情况都比较
			}
			//只有key相同时,value不同时
			else if(temp.key == entry.key && temp.value != entry.value){
				entry.value = temp.value;
				return true;
			}
			//当key不同时,则比较下一个元素
			else if(temp.key != entry.key){
				if(null == entry.next){
					break;
				}
				entry = entry.next;
			}
			}
			//如果没有发现相同的话,则挂在最后面
			this.addLastEntry(entry, temp);
			return true;
		}
		//当entry为null时,将元素放在最开始
		this.addFirstEntry(temp, entryArray, index);
		return true;
	}
	
	/**
	 * 将数据存到Hash表中
	 * @param k
	 * @param v
	 * @return
	 */
	public boolean put(K k,V v){
		int hash = k.hashCode();//首先计算出hash值
		Entry<K,V> entry = new Entry(k,v,hash);
		//将entry加到数组中
		if(setEntry(entry, entryArray)){
			size++;
			return true;
		}
		return false;
	}
	
	public V get (K k){
		//先得到下标
		int hash = k.hashCode();
		int index  = this.indexFor(hash, entryArray.length);
		//得到链表
		Entry<K,V> entry = entryArray[index];
		if(entry == null){
			return null;
		}
		//遍历整个链表。。比较k值是否相等。。相等则返回对应的value
		while(null == entry){
			if(entry.key == k || entry.key.equals(k)){
				return entry.value;
			}
			entry = entry.next;
		}
		//不等的话则返回null
		return null;
	}
	
	/**
	 * 要放的节点要放在某个数组元素(未有元素)中时,将节点设为根节点
	 * @param temp:要放入的链表
	 * @param entryArray:链表放入的数组
	 * @param index:链表在数组中的下标
	 */
	public void addFirstEntry(Entry<K,V> temp,Entry[] entryArray,int index){
		if(size > max){//如果容量超标,则扩容
			this.reHash(entryArray.length*2);
		}
		entryArray[index] = temp;//将链表放在指定的下标中
		temp.next = null;//temp是根节点,后面什么都没有了
	}

	/**
	 * 挂链法:将加点挂到相应节点的后面
	 ***** !!!!!第二个节点参数在后面的!!!!!****(这个顺序要注意!!)
	 * @param entry:要挂在这个节点的后面
	 * @param temp:要挂的节点
	 */
	public void addLastEntry(Entry<K,V> entry,Entry<K,V> temp){
		if(size >max){
			reHash(entryArray.length);
		}
		entry.next = temp;//挂在后面
	}
	
}

 

最后是测试类:

package 哈希表1029;

public class HashTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new HashTest().test(args);

	}
	
	public void test(String[] args){
		Hash<String ,String> hash = new Hash<String,String>();
		
		long beginPut = System.currentTimeMillis();
		for(int i=0;i<1000000;i++){
			hash.put("", ""+i);
		}
		long endPut = System.currentTimeMillis();
		System.out.println("存放数据用时:"+(endPut-beginPut));
		
		long beginGet = System.currentTimeMillis();
		hash.get(""+10000);
		long endGet = System.currentTimeMillis();
		System.out.println("获取数据用时:"+(endGet-beginGet));
	}

}

 

随后最后运行的结果是:



 

希望我的讲解能给大家的Hash表的学习带帮助,也希望大家能够多多给我提建议,大家共同进步!

附件:

Hash表源代码包

  • 描述: 运行结果
  • 大小: 7.4 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics