JDK源码 -- Set

版权声明:转载原创博客请附上原文链接 https://blog.csdn.net/weixin_43495590/article/details/89313825

一:继承结构

Set重要继承结构图

  1. Set系列三个具体实现子类分别是TreeSet、HashSet、LinkedHashSet
  2. Set与List一样同为Collection系列

二:重点结构描述

  1. Set接口继承Collection接口,并没有像List那样提供特殊方法
  2. AbstractSet与AbstractList抽象类相似,继承AbstractColletion实现Set接口
  3. SortedSet接口继承Set接口,提供额外数据截取操作
  4. NavigableSet接口则是针对SortedSet接口方法补充重载边界包含,headSet()默认false不包含最后一个元素,tailSet()默认true包含第一个节点

三:TreeSet

  1. 相对于List而言Set最大的特点就是去重,接下来第一点就是查看Set怎么在添加元素的时候实现的去重
  2. 讨论TreeSet是否可以储存null值
  3. TreeSet如何实现的有序储存即按照某种规则顺序储存数据
3.1 去重实现
  1. 查看TreeSet属性NavigableMap<E,Object> m,该属性赋值在构造函数初始化时进行,默认使用TreeMap,也就是TreeSet底层采用TreeMap结构储存数据
  2. add()方法作为入口查看元素作为TreeMap的key值保存,value值为Object实例。调用TreeMap类put()
  3. 通过TreeMap的put()分析,当key值相同时仅仅修改固定值value,即不会创建新的Entry节点
	// 储存数据结构
	private transient NavigableMap<E,Object> m;

	// 常量值value
    private static final Object PRESENT = new Object();
    
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

	public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
    
    // TreeMap类put方法片段
    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);
3.2 Null值处理
  1. 针对Null值处理还是需要将TreeMap的put方法作为入口跟进
  2. TreeSet实例化未传入Comparator比较器对象则空指针异常
	// TreeMap的put方法如果第一次储存元素调用compare方法
    final int compare(Object k1, Object k2) {
        return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
            : comparator.compare((K)k1, (K)k2);
    }
	// 截取片段put方法,else表示comparator属性为空即初始化未传入Comparator
    else {
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
           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);
    }
3.3 顺序规则

通过上面展示的代码段可以看出TreeSet储存数据时都会根据储存元素实现的Compaable比较器comparaTo()方法,亦或是实例化传入Comparator比较器的compara()方法制定顺序规则。也就说TreeSet储存的元素要么自身实现Comparable接口,要么就需要传入Comparator比较器

四:HashSet

  1. 相较于TreeSet采用HashMap作为底层实现储存数据
  2. HashSet对于Null值处理又是什么规则
4.1 数据储存
  1. HashSet相对于TreeSet来讲少了null值限制,也就是可以储存null
  2. HashSet使用HashMap的kley值储存数据,HashMap中putVal()可以看到当key相同hash值必定相同的情况下是直接覆盖处理,也就是不会有重复值
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; 
        Node<K,V> p; 
        int n, i;
        // HashMap储存数据采用Node数组,该数组未实例化则调用resize()实例化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length; // n记录数组容量
        
        // 根据哈希算法确定元素在数组中下标位置储存
        // 前提是该位置没有元素
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            // 判断hash值与key则直接覆盖(这里就使得HashSet达到了去重目的)
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
        }
        ......
    }

五:LinkedHashSet

继承HashSet,相对于HashSet区别在于底层采用LinkedHashMap实现。也就是可以归结为HashMap与LinkedHashMap的区别,LinkedHashMap使用Entry内部类继承HashMap内部类Node,从而在里面提供了双向链表维护数据插入顺序

猜你喜欢

转载自blog.csdn.net/weixin_43495590/article/details/89313825
set