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,这就说明我们还有很多要改进的地方,当然看系统

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值