hash 表在Java中的应用

1.我们知道的在java中有关hash的常用类有HashTable HashSet HashMap,为什么那为什么叫Hash呢, ArrayList,LinkedList,TreeSet,还有个TreeMap,为什么他们不敢在名字前面加一个Hash呢,,?ArrayList,LinkedList,TreeSet,

2.hash表是一种很重要的数据结构。hash表是什么,hash表是一种映射,我们可以通过一个函数得到一个元素的hash值,然后可以根据这个值来查找这个元素,如果我们得到好的hash函数,那么查找元素的时间复杂度将可以达到O(1)。然而通过hash函数同一个元素得到的hash值,可能是一样的,这种现象叫做冲突。

hash函数的几种构造方法:

1)直接定执法

2)数字分析法

3)平方取中法

4)折叠法

5)除留余数法

6)随机数法

现在就来看看String这个类中重写hashCode()方法时定义的hash函数是什么样子的:

public int hashCode() {
	int h = hash;
	if (h == 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;
    }

可以见得如果String的值相等, 那么hashCode是相等的。即使不是同样的对象。

处理冲突的几种方法:

1)开放定址法

2)在hash法

3)建立一个公共溢出区。

关于hash表的更多内容可以参考数据结构一书,另外一点很重要hash表中是不能有重复的元素的。

3.我们知道HashTable HashSet HashMap(包括TreeMap的键),它们在表中都不能重复,什么叫不能重复,可以参见文档看到这样的话,当执行add,Map的是put方法,有这样一段话:如果 set 中尚未存在指定的元素,则添加此元素(可选操作)。更正式地说,如果此 set 没有包含满足下列条件的元素e,则向 set 中添加指定的元素o(o==null ? e==null :o.equals(e))。如果此 set 已经包含指定的元素,则该调用不改变此 set 并返回 false。结合构造方法上的限制,这就可以确保 set 永远不包含重复的元素。

看源代码如下:

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

可见判断是否相等是分两步来判断的,首先要看hashCode,然后要调用==,个人现在觉得在Object中==与eqauls方法是一个意思,也就是说,当我们用==来判断两个对象是不是相等的时候使用eqauls方法来判断的。当然我们可以在类中从写equals方法,那么== 就与equals不同了,==比较的是两个对象是不是同一个对象。

4.所以有必要来详细的看一看equals方法是怎么回事。看文档:

指示某个其他对象是否与此对象“相等”。

equals 方法在非空对象引用上实现相等关系:

  • 自反性:对于任何非空引用值 xx.equals(x) 都应返回 true
  • 对称性:对于任何非空引用值 xy,当且仅当 y.equals(x) 返回true 时,x.equals(y) 才应返回true
  • 传递性:对于任何非空引用值 xyz,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么x.equals(z) 应返回true
  • 一致性:对于任何非空引用值 xy,多次调用 x.equals(y) 始终返回true 或始终返回false,前提是对象上 equals 比较中所用的信息没有被修改。
  • 对于任何非空引用值 xx.equals(null) 都应返回 false

Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 xy,当且仅当xy 引用同一个对象时,此方法才返回 truex == y 具有值true)。

注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

这个就是equals方法的特点吧。
5.再来看看hashCode是什么?文档:

返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。

hashCode常规协定是:

  • 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
  • 以下情况 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。这个的意思是equals方法返回false,hashCode也是可以一致的。

实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。

上面所说的常规协定很有意思,它的意思是我们要遵循上面的原则,当我们重写HashCode()方法的时候,要遵循那些规定。

6.所以在我们要向HashTable HashSet 里面加入元素的时候如果加入的元素是null,那么如果他们里面已经有null了,那么就不会向里面加入null了,如果要加入的元素不是null,那么先要判断集合中有没有元素的hashCode的值与这个元素的hashCode的值相等了,如果没有相等的,那么直接加入到集合中,如果有hashCode值相等的,那么继续用equals方法判断这两个元素是不是相等,如果equals方法返回false,那么加入这个元素,如果返回true,那么就不加入这个元素到集合中去。

7.HashMap也是类似的,而且如果已经有key与要加入的Key 是重复的(判断方法不再赘述),那么新加入的就会覆盖原来的,而不是不加入。

8.TreeMap有一点的不同,那就是key的值是不能为空的。其他的使用应该与HashMap一样的。TreeMap得到的结果是会根据key的值进行排序的。

下面是TreeMap的put方法的源代码:

public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
	    // TBD:
	    // 5045147: (coll) Adding null to an empty TreeSet should
	    // throw NullPointerException
	    //
	    // compare(key, key); // type check
            root = new Entry<K,V>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<K,V>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

9.文档中有句话是说,当我们重写hashCode()方法是就要重写equals()方法,反之亦然。而且要注意的是重写hashCode()方法要遵循文档中所说的规范。

10.一个重写hashCode(),equals()方法的例子。

public class HashSetTest {
	public static void main(String[] args) {
		HashSet<Student> set = new HashSet<Student>();
		Student s1 = new Student("zhangsan");
		Student s2 = new Student("zhangsan");
		set.add(s1);
		set.add(s2);
		System.out.println(set);
	}
}
class Student {
	String name;
	Student(String name) {
		this.name = name;
	}
	public Student() {
		// TODO Auto-generated constructor stub
	}
	
	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
	}


	public int hashCode() {
		return this.name.hashCode();
	}
	public boolean equals(Object  obj) {
		if (this == obj) {
			return true;
		}
		if (null != obj && obj instanceof Student) {
			Student s = (Student)obj;
			if (name.equals(s.name)) {
				return true;
			}
		}
		return false;
	}
} 

11.关于equals方法的性质,那有一段插曲:

http://topic.csdn.net/u/20110914/18/11d98e4f-ba44-43b0-9dec-a8132b96621e.html

12.总结:通过上面的分析,可见hash表在Java集合类中的应用是很重要的,在查找方面它的时间复杂度是相当的理想。Object中有一个方法叫做hashCode(),说明了每个对象都会有这个方法的,这个方法返回的是hashcode,所以没有对象都有一个hashCode的值,这个值就是对象在内存中的物理地址的十六进制的表示。这里我们可以推测,Java中对象,变量等的存储是采用了hash表来存储的。另外hashCode 与equals这两个东西的关系是异常的紧密,尤其是在Set中的运用。

13.以上仅是个人的总结,还有很多不足甚至错误的地方,希望多多指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值