zoukankan      html  css  js  c++  java
  • set集合

    set集合属于collection集合的一个子类。和List集合不同的是:

    ​ set集合元素具有唯一性

    ​ set集合元素无序

    ​ set集合元素只允许一个null值

    ​ 不是线程安全的(效率高)

    唯一性

    学过list集合的可能都会知道,list集合中添加什么元素就会有什么元素,即使添加多个相同的元素,list集合也都会保存下来。但是对于set集合来说就不是这样了。set集合的很重要的一个特点就是元素的唯一性,即相同的元素只会保存下来一个。

        public static void main(String[] args) {
    
            Set<String> set = new HashSet<>();
            set.add("小明");
            set.add("小红");
            set.add("小红");
            set.add("小青");
            set.add("小明");
            set.add("老王");
            set.add("小蓝");
            set.add("小蓝");
            System.out.println(set);
        }
    

    相应的结果:

    [小明, 老王, 小蓝, 小红, 小青]
    

    可以很清楚的发现,set集合将重复添加的元素过滤掉了。

    但是对于对象类型,好像并不是这样,首先先看一下实例。

        public static void main(String[] args) {
    
            Set<User> set = new HashSet<>();
            set.add(new User("小王",20));
            set.add(new User("小王",20));
            set.add(new User("小蓝",20));
            set.add(new User("小里",20));
    
            System.out.println(set);
        }
    

    对应的结果

    [User{name='小王', age=20}, User{name='小里', age=20}, User{name='小王', age=20}, User{name='小蓝', age=20}]
    

    我们发现,set集合并没有达到我们预期的效果。

    为了解决这个问题,我们需要去阅读一下jdk的源代码。

        static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }   
    	public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    

    通过代码的跟踪,发现set集合的add方法最终调用的是putVal方法,该方法有一个参数hash(key),这个方法返回的就是key值对应的一个hash值。

    贴下putVal的源代码

    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;
        }
    
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
    

    这两句主要就是在set集合中根据当前的hash值查找是否已经存在了一个对象,如果不存在,就会新创建一个对象并保存。

                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
    

    如果已经存在了,就会走else分支,执行上述代码。判断hash值和两个对象的地址是否相等,如果都相等,则代表重复了,就执行去重操作。

    所以想要解决对象不会去重的问题,关键就是要解决hash值和地址的问题。那我们又知道,只有地址相同的对象才是相同的对象,所以如果要实现去重的效果,就要重写对象的equals方法和hashCode方法!

        @Override
        public int hashCode() {
            return this.name.hashCode();
        }
    
        @Override
        public boolean equals(Object obj) {
            boolean bool = false;
            if (obj instanceof User){
                User user = (User) obj;
                if (user.name != null && this.name.equals(user.name) && this.age == user.age){
                    bool = true;
                }
            }
            return bool;
        }
    

    修改equals的判断原则。

    运行结果

    [User{name='小蓝', age=20}, User{name='小里', age=20}, User{name='小王', age=20}]
    

    无序性

    HashSet集合操作,是将数据值存入至HashMap的key中;

    hashmap在保存数据时,顺序是通过计算key的hash值和当前数组长度的 & 运算,计算保存数据的下标位置。所以说set是无序的。


    持续更新~~

  • 相关阅读:
    线程池源码解析
    String与常量池
    spring循环依赖
    ConcurrentHashMap源码解析(JDK8)
    原子类源码分析
    web service和ejb的区别
    RPC
    hashcode()和equals()的区别
    关于json
    Lifecycle of jsf
  • 原文地址:https://www.cnblogs.com/mrjkl/p/13051555.html
Copyright © 2011-2022 走看看