1. 首先明确hash算法:
既然都是HashSet集合了,肯定与hash算法有关,我的理解就像是在查找新华字典(哈希表)一样,按照拼音(哈希值)先找到在哪页(哪个存储区域),再在该页(区域)查找。比全部遍历提高了查找效率。
2. HashSet集合是如何保证唯一性的?
通过追溯add()方法,了解到底层为HashMap的put(K key, V value)方法,源码如下:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key); //与对象的hashCode()有关
int i = indexFor(hash, table.length);//查找hash对应的存储区域位置
//循环倒序比较(先进入的最后比较)该区域的值
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()和equals()方法。
3. 为什么要重写所装入对象的hashCode()和equals()方法?
如果不重写就会用继承自Object类的方法,而Object.hashCode()代表对象的十进制内存地址值,造成哈希值肯定不同,都不用equals()直接导致例如两个同名的学生对象能同时存在,无法满足“定制的”唯一性。
但是如果重写成如下方式了?
public int hashCode() {
return 0;
}
把哈希值变成一样的(0),于是都能在一个区域内用equals()比较对象的成员变量是否相同,如果不同则添加到集合。看起来不错,但是就变成了遍历对象了,效率太低。于是我们需要定制规则让不同的对象哈希值不同,于是与对象的成员变量关联起来,但是简单的成员变量的哈希值相加的和可能会造成一样的哈希值,这就造成了冲突。为了尽可能区分,于是乘以一个数,如下系统生成的hashCode():(为什么是31?http://blog.csdn.net/tayanxunhua/article/details/20525251)
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
这样,通过调用对象hashCode(),相同哈希值的就会放在一起,然后用重写的equals()判断是否成员变量一致;不同的就直接加到集合中。
更进一步,我们或许可以得出这样的结论(其实是规定):
- 如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;
- 如果两个对象的hashCode相同,它们并不一定相同。
当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。