java中Set 和 Map

本文深入解析了Set集合中元素去重的机制,详细分析了HashSet和TreeSet内部如何通过hashCode和equals方法判断元素唯一性。同时,介绍了TreeSet使用Comparable接口的compareTo方法进行元素排序与去重的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

昨天,看到一道关于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表示两节点重复。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值