java集合整理 Set源码解析

Set存储基本数据类型:
由以下代码我们可以看到Set是存取基本数据类型无序,但是如果基于java8的strean流来说却是有序的。普通的增强for循环打印的结果却是无序的,但是可以保证的是都不可以存储相同的元素。

    public static void main(String[] args) throws InterruptedException {

        HashSet hs = new HashSet();
        hs.add("a");
        hs.add("a");
        hs.add("b");
        hs.add("c");
        hs.add("d");
        hs.parallelStream().forEach(x -> System.out.println(x));

        Thread.sleep(1000);

        for (Object object : hs) {
            System.err.println(object);
        }
    }
第一次java8遍历结果:
d
a
b
c
第二次普通循环遍历结果:
a
b
c
d

Set存储自定义数据类型保证元素的唯一性:

package com.ucap.pojo;

public class Student {

    private String  name;

    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }

    public Student(String name, String age) {
        super();
        this.name = name;
        this.age = age;
    }
}
public static void main(String[] args) throws InterruptedException {

        HashSet hs = new HashSet();
        Student st1 =new Student("张三","18");
        Student st2 =new Student("张三","18");
        Student st3 =new Student("李四","33");
        Student st4 =new Student("李四","33");
        Student st5 =new Student("李四","33");
        hs.add(st1);
        hs.add(st2);
        hs.add(st3);
        hs.add(st4);
        hs.add(st5);
        hs.parallelStream().forEach(x -> System.out.println(x));

        Thread.sleep(1000);

        for (Object object : hs) {
            System.err.println(object);
        }
    }
结果:
Student [name=李四, age=33]
Student [name=张三, age=18]
Student [name=李四, age=33]
Student [name=张三, age=18]
Student [name=李四, age=33]

Student [name=李四, age=33]
Student [name=李四, age=33]
Student [name=张三, age=18]
Student [name=张三, age=18]
Student [name=李四, age=33]

可以看到不管是java 8 的stream还是普通的循环都不能保证HashSet元素的唯一性,相投的元素还是存在相同的
我们再来重写其equals方法

@Override
    public boolean equals(Object obj) {
    System.out.println("执行否!");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass()) //字节码对象的比较
            return false;
        Student other = (Student) obj;//向下转型
        if (age == null) {
            if (other.age != null)
                return false;
        } else if (!age.equals(other.age))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

继续遍历:

结果:
Student [name=李四, age=33]
Student [name=张三, age=18]
Student [name=李四, age=33]
Student [name=张三, age=18]
Student [name=李四, age=33]

Student [name=李四, age=33]
Student [name=李四, age=33]
Student [name=张三, age=18]
Student [name=张三, age=18]
Student [name=李四, age=33]

依旧是重复的!因为equals根本都没执行。HashSet肯定跟hash值有关,我们从写其hashCode方法

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((age == null) ? 0 : age.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
执行否!
执行否!
执行否!
Student [name=张三, age=18]
Student [name=李四, age=33]

Student [name=李四, age=33]
Student [name=张三, age=18]

去重成功。
这里写图片描述

没重写equals之前去重无效的原因是,HashSet中采用的hash来确定是否是唯一元素,比如我们上体中的两个name,age对象虽然都是张三,18但由于他们存储的位置不同,所以就会程序就会判断为不同对象,没有必要进行比较,之后就不会去执行equals方法了。所以我们需要来重写hashCode来让其hashcode值相同,这样才能后续的equals逐级的比较。
所以我们想要让Set保证自定义元素的唯一性,就必须重写其hashCode和equals方法。也就是hashcode相同时,才会去调用equals.

这样就延伸出了HashSet添加元素了,肯定会为添加的当前元素赋hash值,我们来看看HashSet是如何添加元素的:

   //HashSet自定义的Map类型的引用
   private transient HashMap<E,Object> map;
   // 添加双列集合时的value是个固定的Object类,也就是说map的value是固定的,我们新增set时添加的key.因此我们发现,单列集合Set底层是双列集合Map实现的。
    private static final Object PRESENT = new Object();

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;//为true的话,表名元素不重复,否则重复。我在List源码的add方法提过。
    }

我大概解释下关键代码

 /**
   *默认定义的容器大小
   */
  transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

 //进入到map的put方法,我们传入的value是固定的
      public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)    
            return putForNullKey(value);
        int hash = hash(key);   //计算传入元素的hash
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) { //遍历容器中的存在的元素
            Object k;
            //若容器中存在的元素与当前元素的hash相同,我们就进行equals方法,如果当前元素对象没重写equals,则执行默认Object中的equals 。
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;   //返回原容器存在的值。
            }
        }
     //hash不同的话,添加entry并返回true,保存成功。
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

LinkedHashSet是HashSet的子类,所以其保证元素的唯一性与父类一致,只不过实现方式是链表方式所以可以保证,存取的顺序一致。我们重点来看下TreeSet.

TreeSet存储String和基本数据类型的

        TreeSet hs = new TreeSet();
        hs.add("a");
        hs.add("a");
        hs.add("d");
        hs.add("b");
        hs.add("c");

        for (Object object : hs) {
            System.err.println(object);
        }
    }
结果:
a
b
c
d

自定义类的排序和元素唯一性的保证我这里用的无参构造器!!!(标红)

package com.ucap.pojo;
//实现Comparable<Student>并声明泛型!
public class Student  implements Comparable<Student>{

    private String  name;

    private String age;

    public String getName() {
        return name;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((age == null) ? 0 : age.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }

    public Student(String name, String age) {
        super();
        this.name = name;
        this.age = age;
    }
//重写方法,返回0说明所有值都重复,后面会贴源码会详解。
    @Override
    public int compareTo(Student o) {
        // TODO Auto-generated method stub
        return 0;
    }
}
    TreeSet<Student> hs = new TreeSet<Student>();
        Student s1 =new Student("赵比","12");
        Student s2 =new Student("王比","88");
        Student s3 =new Student("赵比","12");
        Student s4 =new Student("李比","18");

        hs.add(s1);
        hs.add(s2);
        hs.add(s3);
        hs.add(s4);

        for (Object object : hs) {
            System.err.println(object);
        }
    }

下面贴的两种构造器是为后面的TreeMap中的put做铺垫


    /**
     * Constructs a new, empty tree set, sorted according to the
     * natural ordering of its elements.  All elements inserted into
     * the set must implement the {@link Comparable} interface.
     * Furthermore, all such elements must be <i>mutually
     * comparable</i>: {@code e1.compareTo(e2)} must not throw a
     * {@code ClassCastException} for any elements {@code e1} and
     * {@code e2} in the set.  If the user attempts to add an element
     * to the set that violates this constraint (for example, the user
     * attempts to add a string element to a set whose elements are
     * integers), the {@code add} call will throw a
     * {@code ClassCastException}.
     */
    public TreeSet() {
      //构造方式传入map
        this(new TreeMap<E,Object>());
    }

    /**
     * Constructs a new, empty tree set, sorted according to the specified
     * comparator.  All elements inserted into the set must be <i>mutually
     * comparable</i> by the specified comparator: {@code comparator.compare(e1,
     * e2)} must not throw a {@code ClassCastException} for any elements
     * {@code e1} and {@code e2} in the set.  If the user attempts to add
     * an element to the set that violates this constraint, the
     * {@code add} call will throw a {@code ClassCastException}.
     *
     * @param comparator the comparator that will be used to order this set.
     *        If {@code null}, the {@linkplain Comparable natural
     *        ordering} of the elements will be used.
     */
    public TreeSet(Comparator<? super E> comparator) {
    //构造方式传入map
        this(new TreeMap<>(comparator));
    }

TreeSet可以保证元素的唯一性,和保证排序

    /**
     * The backing map.
     */
    private transient NavigableMap<E,Object> m;
    //添加元素
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

NavigableMap连put方法都没我擦,找它的父类看看,依次SortedMap,Map。还是毛都没有。来看看他们的实现类
这里写图片描述
最后在TreeMap中找到了put方法,其他类都没实现put,所以肯定是TreeMap无疑了


 //map中定义的root根元素
 private transient Entry<K,V> root;    

 public V put(K key, V value) {
        Entry<K,V> t = root;
        //根是否存在,不存在则将当前元素作为根,用于排序。
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);  
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;  //判断当前构造comparator
        if (cpr != null) {  //如果当前构造不为空,则使用传入对象实现的的compare(key, t.key)方法,也就是我们自定义对象比较需要构造比较器;
            do {
               //记录当前元素父节点
                parent = t;
                //当前key,父级节点t比较 (也就是我们重写的compare)
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)  //红黑树比较,当前元素
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value); //等于0的情况就返回原存储数据。
            } while (t != null);
        }
        else {  //如果没有传入构造(也就是我们示例的方式),则向上转型使用父类的compareTo,也就是我们在定义Student时声明的泛型:public class Student  implements Comparable<Student>
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
              //当前k
                Comparable<? super K> k = (Comparable<? super K>) key;//限定其下边界
            do {
                //记录当前父节点
                parent = t;
                //对重写的compareTo进行运算。
                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<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
            //红黑树调整
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

以上就是对TreeSet源码的一些简单认识。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值