【Java Set】1.Set集合的详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/benben513624/article/details/81565168

一、Set

Set注重独一无二的性质,该体系集合可以知道某物是否已经存在于集合中,不会存储重复的元素,用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复

对象的相等性:
引用到堆上同一个对象的两个引用是相等的。如果对两个引用调用hashcode方法,会得到相同的结果,如果对象所属的类没有覆盖object的hashcode方法的话,hashcode会返回每个对象特有的序号(java是依据对象的内存地址计算出的此序号),所以两个不同的对象的hashcode值是不可能相等的。

如果想要让两个不同的Person对象视为相等的,就必须覆盖Object继承下来的hashcode方法和equals方法,因为Object hashcode返回的是该对象的内存地址,所以必须重写hashcode方法,才能保证两个不同的对象具有相同的hashcode,同时也需要两个不同对象比较equals方法返回true。

该集合中没有特有的方法,直接继承自Collection:

/**
 * Collection
 *      \--List
 *          有序(存储顺序和取出顺序一致),可重复
 *      \--Set
 *          无序(存储顺序和取出顺序不一致),唯一
 * HashSet:它不保证set的迭代顺序;特别是它不保证该顺序恒久不变
 * 注意:虽然set集合的元素无序,但是,作为集合来说,它肯定有它自己的存储顺序,
 * 而你的顺序恰巧和它的存储顺序一致,这代表不了有序,你可以多存储一些数据就能看到效果
 **/

案例:set集合添加元素并使用增强for循环遍历

    public static void main(String[] args) {
        //创建集合对象
        Set<String> set=new HashSet<String>();
        //创建并添加元素
        set.add("hello");
        set.add("world");
        set.add("java");
        set.add("java");
        set.add("apple");
        //增强for循环 [world,java,hello,apple]
        for (String s:set){
            System.out.println(s);
        }
    }

二、HashSet

HashSet是一个没有重复元素的集合,它其实是由HashMap实现的,HashMap保存的是建值对,然而我们只能向HashSet中添加Key,原因在于HashSet的Value其实都是同一个对象,这是HashSet添加元素的方法,可以看到辅助实现HashSet的map中的value其实都是Object类的同一个对象。

案例:HashSet存储字符串并遍历

    public static void main(String[] args) {
        //创建集合对象
        HashSet<String> hashSet=new HashSet<String>();
        //创建并添加元素
        hashSet.add("hello");
        hashSet.add("world");
        hashSet.add("java");
        hashSet.add("java");
        //遍历集合 [java,hello,world]
        for(String s:hashSet){
            System.out.println(s);
        }
    }

结果输出的是:java,hello,world,为什么存储字符串的时候,字符串内容相同的只存储了一个呢?思考一下,为什么第二个java没有存进去呢,我们来分析一下add()的源码,了解一下底层是怎么工作的,首先把他们的继承关系理出来,会有一个类HashSet实现Set接口,还会有一个Collection的接口,如下代码:

interface Collection{
    ...
}

interface Set extends Collection{
    ...
}

class HashSet implements Set{

}

紧接着点进去看add()方法的源码,它返回的是map.put(e, PRESENT)==null,传递一个e,传递一个PRESENT变量,PRESENT是一个静态的常量,对我们看代码来说意义不大,我们只需要关注e的值就行,HashSet在它的无参构造里面有一个HashMap,创建了一个HashMap对象,到这里我们就可以知道HashSet的底层其实是HashMap,紧接着我们就继续找HashMap里面的put方法,看它的源码:

class HashSet implements Set{
        private static final Object PRESENT = new Object();
        private transient HashMap<E,Object> map;
        public HashSet() {
                map = new HashMap<>();
            }

        public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }

        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
}

HashSet:哈希表里面存放的是哈希值,HashSet存储元素的顺序并不是按照存入时的顺序(和List显然不同) 是按照哈希值来存的所以取数据也是按照哈希值取得。HashSet不存入重复元素的规则.使用hashcode和equals,由于Set集合是不能存入重复元素的集合。那么HashSet也是具备这一特性的。HashSet如何检查重复?HashSet会通过元素的hashcode()和equals()方法进行判断元素师否重复,当你试图把对象加入HashSet时,HashSet会使用对象的hashCode()方法来判断对象加入的位置。同时也会与其他已经加入的对象的hashCode进行比较,如果没有相等的hashCode,HashSet就会假设对象没有重复出现,简单一句话,如果对象的hashCode值是不同的,那么HashSet会认为对象是不可能相等的,因此我们自定义类的时候需要重写hashCode,来确保对象具有相同的hashCode值。

如果元素(对象)的hashCode值相同,是不是就无法存入HashSet中了? 当然不是,会继续使用equals 进行比较.如果 equals为true 那么HashSet认为新加入的对象重复了,所以加入失败。如果equals 为false那么HashSet 认为新加入的对象没有重复.新元素可以存入。

三、总结

元素的哈希值是通过元素的hashcode()方法来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法 如果 equlas结果为true ,HashSet就视为同一个元素。如果equals 为false就不是同一个元素。

哈希值相同equals为false的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。

HashTable

图1:hashCode值不相同的情况

图2:hashCode值相同,但equals不相同的情况。

HashSet:通过hashCode值来确定元素在内存中的位置。一个hashCode位置上可以存放多个元素。

当hashcode() 值相同equals() 返回为true 时,hashset 集合认为这两个元素是相同的元素.只存储一个(重复元素无法放入)。调用原理:先判断hashcode 方法的值,如果相同才会去判断equals 如果不相同,是不会调用equals方法的。

四、HashSet到底是如何判断两个元素重复

通过hashCode方法和equals方法来保证元素的唯一性,add()返回的是boolean类型,判断两个元素是否相同,先要判断元素的hashCode值是否一致,只有在该值一致的情况下,才会判断equals方法,如果存储在HashSet中的两个对象hashCode方法的值相同equals方法返回的结果是true,那么HashSet认为这两个元素是相同元素,只存储一个(重复元素无法存入)。

注意:HashSet集合在判断元素是否相同先判断hashCode方法,如果相同才会判断equals。如果不相同,是不会调用equals方法的。

HashSet 和ArrayList集合都有判断元素是否相同的方法,

boolean contains(Object o)

HashSet使用hashCode和equals方法,ArrayList使用了equals方法

五、HashSet存储自定义对象

问题:现在有一批数据,要求不能重复存储元素,而且要排序。ArrayList,LinkedList不能去除重复数据。HashSet可以去除重复,但是无序。

没有更多推荐了,返回首页