黑马程序员技术交流社区
标题: 今天面试了,惨痛教训(其实主要讲Set集合) [打印本页]
作者: chslzj 时间: 2013-7-13 22:22
标题: 今天面试了,惨痛教训(其实主要讲Set集合)
本帖最后由 chslzj 于 2013-7-13 22:46 编辑
今天面试,面试老师问了这样一个问题,"Set里面的元素是怎样判断是否重复的?"我答,是通过hashcode方法和equals方法实现的(这里我的表述也有问题,Set自身也有hashcode和equals方法,但是这两个方法做的是Set与Set的比较,里面的元素应该是通过contains方法来判断是否相等,具体的实现应该是元素的hashcode与equals方法来判断的),我觉得面试老师心里面的答案应该不是这个,好吧,这个疑惑一直我心里,面试完了,来逛论坛,发现一帖子,”这几个Java基础面试题不错哟“,
里面的第十三条
13、Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别
答:Set里的元素是不能重复的,那么用iterator()方法来区分重复与否。equals()是判读两个Set是否相等
难道真的是iterator?iterator是迭代啊,迭代怎么判断啊????百度了一下,都是这个答案,我就不相信,我要探究竟!!!
我带着怀疑的态度去查API
首先,Set是一个借口类,这个毫无疑问,然后判断是否重复的方法可以从两个方法里面存在,一个是add(添加成功或者失败,因为是否重复),还有一个是contains(是否含有这个元素),但是Set类源码没有具体的实现过程,这里我就选取了HashSet这个实现类的源码来观察这个问题。
首先,
- /**
- * Returns <tt>true</tt> if this set contains the specified element.
- * More formally, returns <tt>true</tt> if and only if this set
- * contains an element <tt>e</tt> such that
- * <tt>(o==null ? e==null : o.equals(e))</tt>.
- *
- * @param o element whose presence in this set is to be tested
- * @return <tt>true</tt> if this set contains the specified element
- */
- public boolean contains(Object o) {
- return map.containsKey(o);
- }
复制代码 这是hashSet的contains方法,它调用了一个map对象的方法,然后通过这个我们去看看这个map对象是什么吧- private transient HashMap<E,Object> map;
复制代码 下面HashSet的空的构造方法- public HashSet() {
- map = new HashMap<E,Object>();
- }
复制代码 看到这里,我想大家应该都明白了,HashSet其实就是一个包装后的HashMap,并且把HashSet里面的元素作为HashMap的Key值,现在我们去这个HashMap里面去看看他的containsKey方法是怎样实现的。- /**
- * Returns <tt>true</tt> if this map contains a mapping for the
- * specified key.
- *
- * @param key The key whose presence in this map is to be tested
- * @return <tt>true</tt> if this map contains a mapping for the specified
- * key.
- */
- public boolean containsKey(Object key) {
- return getEntry(key) != null;
- }
复制代码 好吧,又失败,它还是调用getEntry(key)方法,判断有没有得到一个元素来实现,继续找getEntry(key)方法- /**
- * Returns the entry associated with the specified key in the
- * HashMap. Returns null if the HashMap contains no mapping
- * for the key.
- */
- final Entry<K,V> getEntry(Object key) {
- int hash = (key == null) ? 0 : hash(key.hashCode());
- for (Entry<K,V> e = table[indexFor(hash, table.length)];
- e != null;
- e = e.next) {
- Object k;
- if (e.hash == hash &&
- ((k = e.key) == key || (key != null && key.equals(k))))
- return e;
- }
- return null;
- }
复制代码 真像了,这个方法首先得到key的hashcode值,再hash一次,得到一个hash的table(table其实是一个Entry[]),然后遍历这个table,如果存在元素,判读元素的hashcode值是否是否与key的hashcode相等并且key指向了同一个元素或者里面的值可以通过equals方法返回真(不等于空我就不说了,感觉不好描述啊),然后选择返回对象e(成功找到对象,说明存在)或者返回null(就是不存在啦),好吧,到这里的结论是,HashSet底层的是由元素的hashcode方法与equals方法得到是否存在相同元素的,当然问题也不是这样结束了,我们还没有看Add方法呢
这是HashSet的Add方法
- /**
- * Adds the specified element to this set if it is not already present.
- * More formally, adds the specified element <tt>e</tt> to this set if
- * this set contains no element <tt>e2</tt> such that
- * <tt>(e==null ? e2==null : e.equals(e2))</tt>.
- * If this set already contains the element, the call leaves the set
- * unchanged and returns <tt>false</tt>.
- *
- * @param e element to be added to this set
- * @return <tt>true</tt> if this set did not already contain the specified
- * element
- */
- public boolean add(E e) {
- return map.put(e, PRESENT)==null;
- }
复制代码 好吧,直接去找hashmap的put方法吧- /**
- * Associates the specified value with the specified key in this map.
- * If the map previously contained a mapping for the key, the old
- * value is replaced.
- *
- * @param key key with which the specified value is to be associated
- * @param value value to be associated with the specified key
- * @return the previous value associated with <tt>key</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>key</tt>.
- * (A <tt>null</tt> return can also indicate that the map
- * previously associated <tt>null</tt> with <tt>key</tt>.)
- */
- 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;
- }
复制代码 这里,如果找到了对于的Key值,就把新的值赋给了旧的值,然后返回了旧的值,上面HashSet判断是否添加成功是根据是否返回Null来判断的,这里显然不是NUll,所以是HashSet的添加返回结果是false,然后我们接着看HashMap的代码,如果for循环执行完了,还没有跳出这个方法,说明就不存在这个对象了,然后返回null,表示添加成功,结束了,这就证明了HashSet是根据元素的hashcode方法与equals方法来判断是否重复的。在开始看Set源码的时候,我就知道他们为什么说是根据iterator来判断是否有重复的了,下面贴源码
- /**
- * Returns <tt>true</tt> if this set contains the specified element.
- * More formally, returns <tt>true</tt> if and only if this set
- * contains an element <tt>e</tt> such that
- * <tt>(o==null ? e==null : o.equals(e))</tt>.
- *
- * @param o element whose presence in this set is to be tested
- * @return <tt>true</tt> if this set contains the specified element
- * @throws ClassCastException if the type of the specified element
- * is incompatible with this set (optional)
- * @throws NullPointerException if the specified element is null and this
- * set does not permit null elements (optional)
- */
- boolean contains(Object o);
- /**
- * Returns an iterator over the elements in this set. The elements are
- * returned in no particular order (unless this set is an instance of some
- * class that provides a guarantee).
- *
- * @return an iterator over the elements in this set
- */
- Iterator<E> iterator();
复制代码坑爹啊有木有,他们是不是草草看了这个源码,把iterator方法的说明看做是contains方法的说明了???!!!!,什么根据iterator方法啊???
我知道我面试成绩应该很差了,面试的时候确实没有解释清楚,但是,我要跟你们讲,别信什么Set是根据iterator来判断是否重复了的屁话了。
--------------------------------------------------------------------------------------------------------------------
好吧,如果我讲的有错误,请你们指出来,我虚心接受
ps.版主,手抖一抖,技术分,额
作者: 风乐 时间: 2013-7-13 22:29
lz面试多少分,话说我回答的也是hashCode()和equals()方法来判断元素是否相同的,顶楼主
作者: chslzj 时间: 2013-7-13 22:31
还有N多错误,N多不会,惨痛经历,讲出来就是泪,不讲了
作者: 花心々小土豆 时间: 2013-7-14 14:57
我觉得应该是equals()方法吧,HashSet中有hashCode(),如果是TreeSet呢?
就是往Set集合中添加元素时,它会通过equals()方法和集合里的元素比较,然后确保唯一。
HashSet中元素的存储方法和其他不太一样,它是先根据hashCode()得到一个值,这个值会减少新加进来元素与已有元素的比较次数,就是hashCode()得到的值下可能只有很少的几个元素,然后还是用equals()判断是不是重复元素。
TreeSet底层是二叉树存储结构,就是规定根结点的左边比右边的元素小,进来一个新的元素肯定能找到它对应的位置,如果有元素和它相同(用equals()判断)不存。
作者: chslzj 时间: 2013-7-14 17:07
我就看了hashSet的源码,其他的源码就没有看了:P
作者: a767175432 时间: 2013-7-14 19:00
Set集合是通过equals方法和hashcode方法来判断的,
作者: zhou5852 时间: 2013-7-14 19:07
如果是我说 我就说 Set 是一接口 我的子类中想怎么设计判断都行,我可以只通过hashcode()判断 也可以通过equals()方法判断。。。也可以结合,我还可以在我的对象中再添加一个函数 在Set的类中判断。。。。
作者: 赵乐 时间: 2013-7-15 00:05
我觉得老师没有想要你回答这么详细, 可能是要你回答他两个子类的分别实现唯一的方法吧
Set
(1)元素无序,唯一。
(2)Set的两个子类:
|--HashSet
无序,唯一。
如果保证的呢?
它依赖于两个方法:hashCode()和equals()
首先判断hashCode()值是否相同:
是:继续equals()方法
返回值true:说明元素重复,不添加。
返回值false:直接添加到集合中
否:直接添加到集合中。
|--LinkedHashSet
有序,唯一。
通过链表结构保证有序。
通过哈希表结构保证唯一。
|--TreeSet
有序,唯一
如何保证的呢?
它根据返回值是否是0来判断是否重复。
实现方案:
方式1:元素本身具备比较性
实现Comparable接口
方式2:集合具备比较性
实现Comparator接口
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |