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
方法在非空对象引用上实现相等关系:- 自反性:对于任何非空引用值
x
,x.equals(x)
都应返回true
。 - 对称性:对于任何非空引用值
x
和y
,当且仅当y.equals(x)
返回true
时,x.equals(y)
才应返回true
。 - 传递性:对于任何非空引用值
x
、y
和z
,如果x.equals(y)
返回true
,并且y.equals(z)
返回true
,那么x.equals(z)
应返回true
。 - 一致性:对于任何非空引用值
x
和y
,多次调用 x.equals(y) 始终返回true
或始终返回false
,前提是对象上equals
比较中所用的信息没有被修改。 - 对于任何非空引用值
x
,x.equals(null)
都应返回false
。
Object
类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值x
和y
,当且仅当x
和y
引用同一个对象时,此方法才返回true
(x == y
具有值true
)。注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
这个就是equals方法的特点吧。
- 自反性:对于任何非空引用值
返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,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.以上仅是个人的总结,还有很多不足甚至错误的地方,希望多多指出。