天天看点

java8 HashSet,LinkedHashSet,TreeSet接口实现源码解析

一、类继承关系

java8 HashSet,LinkedHashSet,TreeSet接口实现源码解析
java8 HashSet,LinkedHashSet,TreeSet接口实现源码解析
java8 HashSet,LinkedHashSet,TreeSet接口实现源码解析

二、接口功能概述

Iterable接口包含的方法如下,实现该接口,则可以用for循环的方式遍历集合。

java8 HashSet,LinkedHashSet,TreeSet接口实现源码解析

for循环遍历的核心在iterator()方法返回的迭代器Iterator,spliterator()方法返回的Spliterator用于并行流中将一个计算任务拆分成多个并行任务。Iterator接口包含的方法如下:

java8 HashSet,LinkedHashSet,TreeSet接口实现源码解析

 Iterator接口主要用于取代低版本的Enumeration接口来遍历集合元素,相比原来的Enumeration接口增加了对删除元素的支持,并优化了接口方法的命名,注意remove()方法是删除上一次调用next()方法返回的元素,并且只能调用一次,重复调用会抛出IllegalStateException异常,参考如下用例:

@Test
    public void test10() throws Exception {
        Set<String> set=new HashSet<>();
        set.add("test");
        set.add("test2");
        set.add("test3");
        set.add("test4");
        set.add("test5");
        Iterator<String> iterator=set.iterator();
        while (iterator.hasNext()){
            String curr=iterator.next();
            System.out.println(curr);
            if(curr.equals("test")){
                iterator.remove();
                iterator.remove();
            }
        }
    }
           

 Collection接口是集合类接口的根接口,包含的方法如下:

java8 HashSet,LinkedHashSet,TreeSet接口实现源码解析

注意toArray()返回的是Object[],不能强转成T[],只能在Object[]遍历时将Object强转成目标类型的元素。toArray(T[]) 方法的入参是一个目标类型的数组,如果集合中元素的个数大于入参数组的长度则返回一个新数组,否则将集合中的元素添加到入参数组中并返回。removeAll()方法和retainAll()方法是相反的,前者是删除所有在特定集合中的元素,后者是只保留在特定集合中的元素。参考如下测试用例:

@Test
    public void test2() throws Exception {
        Set<String> set=new HashSet<>();
        set.add("test");
        set.add("test21");
        set.add("test3");
        set.add("test4");
        set.add("test5");
//        String[] array=(String[])set.toArray();//抛出异常ClassCastException
        Object[] strs=set.toArray();
        for(Object str:strs){
            System.out.println((String)str);
        }
        String[] strs2=set.toArray(new String[0]);
        for(String str:strs2){
            System.out.println(str);
        }
        String[] strs3=new String[set.size()];
        String[] strs4=set.toArray(strs3);
        System.out.println("strs3==strs4:"+(strs3==strs4));
        for(String str:strs4){
            System.out.println(str);
        }
    }

    @Test
    public void test3() throws Exception {
        Set<String> set=new HashSet<>();
        set.add("test");
        set.add("test2");
        set.add("test3");
        set.add("test4");
        set.add("test5");
        set.add("test6");
        set.add("test7");
        set.add("test8");
        Set<String> test=new HashSet<>();
        test.add("test");
        test.add("test4");
        test.add("test8");
        System.out.println("=====removeAll=======");
        boolean result=set.removeAll(test);
        if(result){
            for(String str:set){
                System.out.println(str);
            }
        }
        Set<String> test2=new HashSet<>();
        test.add("test7");
        test.add("test4");
        test.add("test5");
        System.out.println("=====retainAll=======");
        result=set.retainAll(test);
        if(result){
            for(String str:set){
                System.out.println(str);
            }
        }
    }
           

Set接口继承自Collection接口,没有添加新方法,Set接口表示不允许元素重复的集合,元素可以为null,但是最多只能有一个null,包含的方法如下:

java8 HashSet,LinkedHashSet,TreeSet接口实现源码解析

SortedSet表示一个按照元素实现的Comparable接口或者集合类初始化时传入的Compartor实例排序好的Set集合,方法定义同SortedMap。如下图:

java8 HashSet,LinkedHashSet,TreeSet接口实现源码解析

NavigableSet扩展自SortedSet,增加了按照指定范围查找的导航相关方法,方法定义同NavigableMap,包含的方法如下:

java8 HashSet,LinkedHashSet,TreeSet接口实现源码解析

 三、接口实现

1、AbstractCollection

     AbstractCollection是Collection接口的基础实现类,子类只需要实现size(),iterator()即可,此时集合是不可修改的,覆写add()方法和remvoe()方法,将集合变成可写的集合。其关键代码如下:

public Object[] toArray() {
        //根据size()返回值初始化Object[],因为迭代器在遍历集合元素时不允许添加元素,但是可以删除元素,所以遍历完成实际
        //获取到的元素个数可能比size()的返回值小
        Object[] r = new Object[size()];
        Iterator<E> it = iterator();
        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) //集合元素被删除了,实际获取的元素个数比size()的返回值小
                return Arrays.copyOf(r, i);//此处返回了一个新的数组,确保新数组中的元素全部是集合中的元素,不存在null
            r[i] = it.next();
        }
        //如果集合元素个数大于size()返回值(实际不可能),会重新获取一遍
        return it.hasNext() ? finishToArray(r, it) : r;
    }

    //如果a的长度大于或者等于实际元素个数,则将元素填充到a中并返回a,否则重新初始化一个与元素个数一致的数组,将元素填充到新数组中
    //并返回该新数组
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        int size = size();
        //如果a的大小小于size则通过反射重新初始化一个数组
        T[] r = a.length >= size ? a :
                  (T[])java.lang.reflect.Array
                  .newInstance(a.getClass().getComponentType(), size);
        Iterator<E> it = iterator();

        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) { // 元素个数小于size()返回值
                if (a == r) {
                    r[i] = null; // 未重新分配一个数组则将显示将r[i]置为null
                } else if (a.length < i) {//a的长度小于实际元素的个数
                    return Arrays.copyOf(r, i);//根据实际元素个数将原来的数组复制到一个新数组中
                } else {//a的长度大于或者等于实际元素的个数,将元素复制到a里面
                    System.arraycopy(r, 0, a, 0, i);
                    if (a.length > i) {
                        a[i] = null;
                    }
                }
                return a;
            }
            r[i] = (T)it.next();
        }
        //如果新添加了元素则根据迭代器继续添加元素到r中
        return it.hasNext() ? finishToArray(r, it) : r;
    }

   
   private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
        int i = r.length;
        while (it.hasNext()) {
            int cap = r.length;
            //当i==cap时表示当前数组已经填满了,需要扩容
            if (i == cap) {
                //每次扩容都是扩容50%
                int newCap = cap + (cap >> 1) + 1;
                // 容量过大
                if (newCap - MAX_ARRAY_SIZE > 0)
                    //设定容量上限
                    newCap = hugeCapacity(cap + 1);
                //将原来的数组复制到扩容后的数组
                r = Arrays.copyOf(r, newCap);
            }
            //新数组填充值
            r[i++] = (T)it.next();
        }
        //如果数组被填充满了则直接返回该数组,否则根据元素个数将数组中的元素重新拷贝到一个新数组并返回该数组
        return (i == r.length) ? r : Arrays.copyOf(r, i);
    }
           

2、AbstractSet

 AbstractSet继承自AbstractCollection,是Set接口的基础实现类,只是简单改写了AbstractCollection中的equals(),hashCode(),removeAll()方法,如下图:

java8 HashSet,LinkedHashSet,TreeSet接口实现源码解析

3、HashSet

HashSet内部是借助HashMap实现的Set接口,所有元素的操作都是借用HashMap的方法,HashSet中所有元素都作为key,使用默认的value保存到HashMap中,因此特性同HashMap,支持null元素,非线程安全,遍历时修改会快速失败,具体实现如下:

//实际保存元素的map
    private transient HashMap<E,Object> map;

    // 作为默认的Value
    private static final Object PRESENT = new Object();

    public HashSet() {
        map = new HashMap<>();
    }

    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }


    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

    /**
     * 仅被LinkedHashSet使用
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

    //通过map的keySet()方法返回key的视图
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }


    public int size() {
        return map.size();
    }


    public boolean isEmpty() {
        return map.isEmpty();
    }


    public boolean contains(Object o) {
        return map.containsKey(o);
    }


    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }


    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }


    public void clear() {
        map.clear();
    }
           

4、LinkedHashSet

LinkedHashSet继承自HashSet, 唯一的区别是保存元素的不是HashMap而是LinkedHashMap,且采用默认的插入顺序作为元素遍历的顺序,具体如下:

//最后一个参数true没有任何实际意义,只是为了区分对应的构造方法
    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }


    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }


    public LinkedHashSet() {
        super(16, .75f, true);
    }


    public LinkedHashSet(Collection<? extends E> c) {
        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
    }
           

5、TreeSet

实现原理同HashSet,不过保存元素的Map改成了NavigableMap,默认的实现类是TreeMap,具体如下:

//实际保存元素的Map
    private transient NavigableMap<E,Object> m;

    //默认的value
    private static final Object PRESENT = new Object();


    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }

    //默认放到TreeMap中保存
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }


    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }


    public TreeSet(Collection<? extends E> c) {
        this();
        addAll(c);
    }

    public TreeSet(SortedSet<E> s) {
        this(s.comparator());
        addAll(s);
    }


    public Iterator<E> iterator() {
        return m.navigableKeySet().iterator();
    }

    public Iterator<E> descendingIterator() {
        return m.descendingKeySet().iterator();
    }


    public NavigableSet<E> descendingSet() {
        return new TreeSet<>(m.descendingMap());
    }


    public int size() {
        return m.size();
    }


    public boolean isEmpty() {
        return m.isEmpty();
    }


    public boolean contains(Object o) {
        return m.containsKey(o);
    }


    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }


    public boolean remove(Object o) {
        return m.remove(o)==PRESENT;
    }

    /**
     * Removes all of the elements from this set.
     * The set will be empty after this call returns.
     */
    public void clear() {
        m.clear();
    }
           

继续阅读