`
lsx111
  • 浏览: 14660 次
  • 性别: Icon_minigender_1
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

Hash算法

阅读更多

 

Hash算法分析

学了这么久的java,但一直不知道HashCode到底是怎么回事,现在对HashCode做一下简单的分析。

java中集合分为两类,一类是List,一类是Set。前者是有序的,可以存储重复的元素,后者是无序的,不可存放重复的元素。那现在

就有一个问题,Set是怎么判断一个元素是否重复的。Object类中有个equals()方法,用它来判断一个元素是否重复,但如果每添加一

个元素就进行一次判断,那当元素过多的时候,很明显会降低效率,于是有了哈希表的原理。HashCode的方法实际上是返回了对象的地址

当存放一个元素时会调用Hashcode方法,如果返回的地址没有存放对象时就直接把要存放的对象存入,如果相同,则进行equals()来比

较两个对象是否相同,如果相同则不再存入,否则进行散列,存放到其他位置。所以比较的次数减小了,(1--2次)效率自然就高了。

这里我们首先要明白一个问题: 

equals()相等的两个对象,hashcode()一定相等; 

equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。换句话说,equals()方法不相等的两个对象,hashcode()有

可能相等。(我的理解是由于哈希码在生成的时候产生冲突造成的)。 反过来:hashcode()不等,一定能推出equals()也不等;

hashcode()相等,equals()可能相等,也可能不等。

//测试代码

public class Test1 {
	public static void main(String []arg){
		String s1 = new String("aaa");
		String s2 = new String("aaa");
		String s3 = "aaa";
		String s4 = "aaa";
		System.out.println(s1 == s2);			//false
		System.out.println(s3 == s4);			//true
		System.out.println(s1 == s3);			//false
		System.out.println(s1.hashCode());		//96321
		System.out.println(s2.hashCode());		//96321
		System.out.println(s3.hashCode());		//96321
		System.out.println(s4.hashCode());		//96321
	}
}

 上述代码说明两个对象不相等,但hashCode可能相同。

下面我们一起来分析一下hashcode在HashSet,HashMap和Hashtable中的使用。

HashSet是实现了Set接口,存放的元素时无序性的,不可重复的,那它是怎么判断一个元素是否重复的呢,我们来分析一下。

java中判断两个元素相等的规则有两点:

1),判断两个对象的hashCode是否相等 

      如果不相等,认为两个对象也不相等,完毕 

      如果相等,转入2) 

(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必

需的。) 

2),判断两个对象用equals运算是否相等 

      如果不相等,认为两个对象也不相等 

      如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键) 

为什么是两条准则,难道用第一条不行吗?不行,因为前面已经说了,hashcode()相等时,equals()方法也可能不等,所以必须用第2条

准则进行限制,才能保证加入的为非重复元素。

 

//测试代码
/**
 *学生类
 */
public class Student {
	String name;
	public Student(String name){
		this.name = name;
	}
}
//***************************************************
import java.util.HashSet;
import java.util.Iterator;

public class Test2 {
	public static void main(String []arg){
		Student s1 = new Student("zhao");
		Student s2 = new Student("qian");
		Student s3 = new Student("sun");
		Student s4 = new Student("zhao");
		HashSet<Student> set = new HashSet<Student>();
		set.add(s1);
		set.add(s2);
		set.add(s3);
		set.add(s4);
		System.out.println(s1.hashCode());
		System.out.println(s4.hashCode());
		Iterator<Student> it = set.iterator();
		while(it.hasNext()){
			System.out.println(it.next().name);
		}
	}
}
输出结果:
427340025
594763966
zhao
zhao
qian
sun

 这个结果说明了set中存放了两个相同的元素。那是不是它违背了Set存放元素的原则呢?其实并不是的。那为什么用String创建的两个对象

的hashcode值是相同的呢。

String中的hashcode方法:

 

public int hashCode() {
        int h = hash;
        if (h == 0 && count > 0) {
            int off = offset;
            char val[] = value;
            int len = count;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
    }

 它是重写了父类的hashcode方法,而我们定义的Student类并没有重写Object类的hashcode方法,因此他会存入两个相同的元素。

对Student类进行修改,代码如下:

 

public class Student {
	String name;
	public Student(String name){
		this.name = name;
	}
	//重写父类的hashCode方法
	public int hashCode(){
		return this.name.hashCode();
	}
	//重写equals方法
	public boolean equals(Object o){
		Student s = (Student)o;
		return s.name.equals(this.name);
	}
}
再次运行
结果:
3737564
3737564
zhao
qian
sun

 这样它就不会再加入相同的元素了。

这里要进一步说明一下,判断两个对象是否相同,用equals方法完全可以判断,加hashcode方法并不是必须的,而加hashcode方法主要

是为了提高效率,因为如果每次都进行equals方法进行判断的话会降低效率。(为什么就不用再解释了)

明白了Hash的原理,我们就可以自己写一个Hash的程序了。最基本的功能(增,删,改,查),当处理比较多的数据时,我们看看自己

写的Hash集合是否能应付得了这些数据。

我们自己写一个Hash集合的时候要注意,如果我们把每个数据看成一个节点,那我们首先要考虑的是用什么数据结构来存放这些节点,如

果用数组虽然会方便查找,但对处理大数据的时候则要考虑内存是否够用,而且对数据的增删操作会比较复杂。而用链表有不方便查找,

所以我们要用两者的结合体,创建一个数组,每个单元存放一个链表。首先我们通过自己写的hashcode方法确定该节点要放的位置,如果

没有冲突,则放入。否则放到该节点所在的链表上。

 

//确定节点存放位置的方法(求余数)
	public int getIndex(HashCode code, int size) {
		return code.getKey() % size;
	}

 另一点我们要考虑的是我们不能无限的存放数据,首先我们创建的是数组,它有固定长度,数据存放的越多,造成的冲突就越多,因此我们

要考虑当数据超过一个标准时要重新创建一个数组,然后把原来所有的节点重新排列到新的数组中。我们把这个标准叫做阀值。

下面是具体操作的代码:

 

/**
	 * 加hash节点
	 */
	public void addCode(HashCode newcode) {
		if (length / size == load) { // 如果超过阀值
			System.out.println("");
			System.out.println("reflash");
			reflash(); // 重新分配节点
		}
		int index = getIndex(newcode, size); // 得到索引
		if (HashArray[index] == null) { // 当前位置还没放入元素
			HashArray[index] = newcode; // 将节点加入数组
			HashCode lastcode = null;
			newcode.setNext(lastcode);
		} else {
			HashCode code = HashArray[index];
			newcode.setNext(code.getNext());
			code.setNext(newcode);
		}
		length++;
	}

	/**
	 * 删除节点
	 * @param key	节点的key
	 * @param data	节点的data
	 */
	public void deleteCode(int key, int data) {
		int lay = key % size;
		if (HashArray[lay].getKey() == key && HashArray[lay].getData() == data) {
			HashArray[lay] = HashArray[lay].getNext();
		} else {
			HashCode code = HashArray[lay];
			while (code.getNext().getData() != data
					&& code.getNext().getKey() != key) {
				code = code.getNext();
			}
			code.setNext(code.getNext().getNext());
		}
	}
	
	/**
	 * 重新分配数组中所有节点
	 */
	public void reflash() {
		HashCode newHashArray[] = new HashCode[size + 5]; // 创建一个新数组
		for (int i = 0; i < size; i++) { // 遍历所有节点
			HashCode code = HashArray[i];
			while (code != null) {
				HashCode newcode = code;
				index = getIndex(newcode, (size + 5));
				if (newHashArray[index] == null) {
					newHashArray[index] = newcode;
					HashCode lastcode = null;
					newcode.setNext(lastcode);
				} else {
					newcode.setNext(newHashArray[index].getNext());
					newHashArray[index].setNext(newcode);
				}
				code = code.getNext();
			}
		}
		HashArray = newHashArray;
		size += 5;
		System.out.println("扩充后size:" + size);
	}

 简单的hash集合写完之后并不代表结束,我们可以进行一下测试,然后与系统的Hash进行比较。下面说一下我自己的比较结果。

只是加入节点的操作(10000个节点),我的运行时间是500ms,而系统的只要20ms,这就说明我们还有很多要改进的地方,当然看系统

的源代码是最好的方法,但由于我一直没耐心把系统的程序看完,所以现在还不知道该怎么改。

分享到:
评论
1 楼 野狼yela 2012-11-18  
“equals()相等的两个对象,hashcode()一定相等“真是这样吗?hashCode方法和equals方法都是可以重写的,只是一般情况下,为了保证集合里的元素不出现重复或查询时的冲突
,hashCode和equals方法写成一致,即类的两个对象的hashCode返回相同的数值时,equals也应该返回true,反之亦然

相关推荐

    高运算性能,低碰撞率的hash算法MurmurHash算法.zip

    MurmurHash算法由Austin Appleby创建于2008年,现已应用到Hadoop、libstdc 、nginx、libmemcached,Redis,Memcached,Cassandra,HBase,Lucene等开源系统。2011年Appleby被Google雇佣,随后Google推出其变种的...

    图像的相似度Hash算法(aHash的delphi实现).rar

    在IT领域,Hash算法是一种广泛应用于数据验证、存储和比较的技术。它将任意长度的数据转换成固定长度的输出,通常称为Hash值或指纹。在这个压缩包中,我们重点关注的是图像的相似度Hash算法,特别是平均哈希算法(a...

    Java实现GeoHash算法

    Java实现GeoHash算法是一种在IT领域中用于地理位置数据存储和检索的技术。GeoHash将经纬度坐标转换为字符串,使得地理位置可以被高效地索引和查询。这种算法利用了空间分割和编码策略,使得相邻的位置在编码后具有...

    20多个常用的Hash算法C++ 实现

    Hash函数集合,包含主流的hash函数: nginx_hash算法,OpenSSL_hash算法,RSHash,JSHash,PJWHash,ELFHash,BKDRHash,DJBHash,DEKHash,APHash等等!

    geohash算法实现Java代码

    GeoHash算法是一种基于地理坐标的分布式空间索引技术,它通过将地球表面的经纬度坐标转化为可比较的字符串,使得我们可以高效地进行地理位置的搜索、范围查询以及邻居查找等操作。这种算法尤其适用于大数据和分布式...

    Hash算法MD5实验报告材料.doc

    "Hash算法MD5实验报告材料" 本实验报告主要介绍了Hash算法MD5的实验报告,旨在通过实际编程来了解MD5算法的加密和解密过程,并加深对Hash算法的认识。 一、Hash算法的定义 Hash算法是一种将输入数据转换为固定...

    常见的HASH算法

    ### 常见的HASH算法解析 在计算机科学领域,哈希算法(HASH算法)是一种将任意长度的数据映射到固定长度数据的算法,通常用于数据查找、密码存储、消息完整性验证等多种场景。本文将详细介绍几种常见的哈希算法,...

    几种经典的Hash算法的实现(源代码)

    ### 经典Hash算法概述与实现 #### 一、引言 哈希算法在计算机科学领域扮演着极其重要的角色,特别是在数据检索、信息安全以及数据完整性校验等方面。它能够将任意长度的数据转换成一个固定长度的哈希值,这一过程在...

    hash算法工具类

    一个hash算法的工具类,里面包含了一些常用的hash算法

    hash算法相关介绍

    ### Hash算法相关介绍 在计算机科学领域,哈希(Hash)是一种将任意长度的数据映射为固定长度数据的技术。哈希算法广泛应用于多种场景中,包括但不限于数据完整性验证、密码存储、快速查找等。本文主要介绍了几种...

    一致性 hash 算法

    ### 一致性 Hash 算法详解 #### 一、引言 一致性 Hash 算法是一种特殊的哈希算法,主要用于解决分布式系统中节点增删时数据重定位的问题。该算法最早于1997年在《Consistent hashing and random trees》这篇论文中...

    C语言实现hash算法

    在IT领域,哈希算法(Hash Algorithm)是一种用于将任意长度的数据转化为固定长度输出的算法。这个过程通常称为哈希或散列。哈希算法在信息安全、数据完整性验证、密码学等多个方面都有着广泛的应用。本项目是用...

    常用的hash算法(java实现)

    在计算机科学中,哈希(Hash)算法是一种用于将任意长度的数据映射为固定长度输出的函数。这种输出通常称为哈希值或消息摘要。在Java编程语言中,实现哈希算法可以方便地用于数据验证、查找表以及密码存储等多种用途...

    实验五:安全Hash算法SHA-1的实现

    ### 安全Hash算法SHA-1的实现 #### 一、Hash函数与数据完整性 Hash函数在现代密码学中扮演着至关重要的角色,它能够确保数据的完整性和一致性。一个典型的Hash函数接受任意长度的数据输入,并产生固定长度的输出,...

    Hash算法介绍PPT-英文版

    ### Hash算法介绍 #### 一、Hash算法概述 Hash算法是一种在计算机科学中广泛应用的数据处理技术,主要用于将输入数据(通常称为“键”)转换为固定长度的输出(通常称为“哈希值”或“散列值”)。这种转换过程是...

    geohash算法实现

    Geohash算法实现,经纬度到geohash编码的实现

    geohash算法mysql版代码

    网上有很多geohash算法的实现,都是基于java或者php代码实现的,没有sql实现的版本,这里使用mysql简单实现了这个算法

    Hash算法实验

    Hash算法在IT行业中扮演着至关重要的角色,尤其是在信息安全和数据完整性验证方面。本实验主题为“Hash算法实验”,主要涉及的是密码学中的消息摘要技术,具体是使用MD5(Message-Digest Algorithm 5)算法对文件...

    Geohash 算法的純 C 實現 将所在地球位置经纬度编解码為一定格式字串 有志於開發外送派單工程師請享用~~

    Geohash算法就是将经纬度编码,将二维变一维,给地址位置分区的一种算法 此檔案為C語言實現 函式庫使用介紹: 1)編碼 char* geohash_encode(double lat, double lng, int precision); 以所需精度獲取緯度和經度並...

Global site tag (gtag.js) - Google Analytics