昨天,看到一道关于set的讨论:Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?很多人给出的答案是:Set里的元素是不能重复的,那么用iterator()方法来区分重复与否。equals()是判读两个Set是否相等。
对于这个答案,我不理解!首先,我觉得Set只是一个接口,不同实现(比如HashSet,TreeSet)的判断 方式应该有差异!其次,Set的iterator()方法只是得到一个迭代器而已,遍历出set中元素罢了。遍历期间并不会对对象是否重复进行比较!要看Set是用什么方法区分是否重复应该看add()方法的实现,因为,一旦可以添加,即表示不重复。
我这里读了HashSet 和TreeSet两个实现的源代码:
首先:HastSet底层的数据结构是Hash表,Hash表的优势是查询速度快(最坏情况下除外)。Hash表具体实现请google。
进入正题:Hashset如何判断元素是否重复?我们知道,Hashset其实是一个HashMap。HashSet.add(E e)方法里也只是调用了map.put(e,Obj);代码:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
这里PRESENT是个常量,是一个最简单的对象,所有Set集合中都隐藏了这样一个value!继续跳入,跳入到HashMap的put()方法:
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))) { //首先判断Hashcode是否相同,然后在判断“==”和equals方法
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
由此可以看出,其实HashSet比较对象是否重复首先比较两者的hashcode方法是否相等,如果相等会比较对象的equals()方法,和“==”方法!如果不HashCode都相等,便不会比较equels()和“==”方法了。
我感觉源代码这里有点复杂了,因为“==”操作符表示对象是否相同,既然对象相同即(k = e.key) == key返回true,那么表达式key.equals(k)必然返回true。如果对象不是同一个对象,即(k = e.key) == key返回false,那么表达式key.equals(k)依然有可能返回true。
再来看看TreeSet如何实现的:
首先:TreeSet底层的数据结构是RBTree,红黑树有更高的效率。红黑树具体实现请google。本人也是一知半解!
同样,我们应该看Treeset的add()方法:同样,看到了TreeMap的put()方法,看代码:
public V put(K key, V value) {
Entry<K,V> t = root; //根节点
if (t == null) { //根节点为空,即map的第一个元素
// 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();
//存入TreeSet的对象,也就是TreeMap的key值必须实现Comparable接口!实现compareTo()方法,此方法放回值等于0,即表示两对象重复!!
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;
}
上面的源码,我加了自己的一点点小注释!可以看出来,TreeSet和TreeMap判断其中元素是否重复是根据对象的compareTo()方法的返回值。返回值>0表示新来节点大于原来节点,并存储与该节点右枝!返回值<0表示新来节点小于原来节点,并存储与该节点左枝(当然还会和左孩子节点比较)!返回值=0表示两节点重复。