Set JDK1.8源码(八)——java.util.HashSet 类 JDK1.8源码(八)——java.util.HashSet 类

HashSet概况:

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable

HashSet源码:

常量: 第一个定义一个 HashMap,作为实现 HashSet 的数据结构;第二个 PRESENT 对象,因为前面讲过 HashMap 是作为键值对 key-value 进行存储的,而 HashSet 不是键值对,那么选择 HashMap 作为实现,其原理就是存储在 HashSet 中的数据 作为 Map 的 key,而 Map 的value 统一为 PRESENT

    transient HashMap<E,Object> map;  // HashSet底层实现就是HashMap 
    private static final Object PRESENT = new Object(); // 这个PRESENT相当于HashMap中的value值
View Code

构造方法:从这些构造方法可以看出,初始化一个HashSet,实质上底层是初始化一个HashMap;最后一个是初始化LinkedHashMap,注意修饰符关键字,而且目前所知是为LinkedHashSet而服务的

    public HashSet() {
        map = new HashMap<>();
    }
    
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }
    
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
    
    /*
     *     注意这个构造方法没有修饰符修饰,说明是这个方法只对所在的包可见
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
View Code

 add(E e):通过 map.put() 方法来添加元素,在上一篇博客介绍该方法时,说明了该方法如果新插入的key不存在,则返回null,如果新插入的key存在,则返回原key对应的value值(注意新插入的value会覆盖原value值)。

     也就是说 HashSet 的 add(E e) 方法,会将 e 作为 key,PRESENT 作为 value 插入到 map 集合中,如果 e 不存在,则插入成功返回 true;如果存在,则返回false。

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

 其它方法是类似的,只要理解了HashMap,就很简单了,详情请看我也是看这个的:JDK1.8源码(八)——java.util.HashSet 类

HashSet对象重复问题以及hashCode(),equal()方法:

关于set对象里面的重复:

class Person {
    private String name;
    private int age;
        ...... 
}
public class SetTest {
    public static void main(String[] args) {
        Set<Person> set = new HashSet<>();
        set.add(new Person("张三", 14));
        set.add(new Person("李四", 16));
        set.add(new Person("张三", 14));
        set.add(new Person("李四", 16));
        set.add(new Person("张三", 14));
        System.out.println(set);
    }
}
View Code

 明显6个元素都输出,因为每个对象的地址值都不一样嘛(每个对象的那个hashcode值不一样)

class Person {
    private String name;
    private int age;
    ......
    @Override                                                
    public boolean equals(Object obj){                       
       Person p = (Person)obj;                              
       return this.name.equals(p.name) && this.age == p.age;
   }                                                        
}
View Code

复写equals方法,一样没有用,因为调用equals方法之前,一定会调用hashcode方法,因为每个对象里面的hashcode值 是不一样的,所以当发现hashcode值不一样时,就不再调用equals方法了(无论这个时候equals方法后是否一样),只有当hashcode值相同的时候才去调用equals方法。但是,如果只重写了hashcode方法不重写equals方法,虽然hash值相同,但是因为每个对象的地址值不一样,所以还是输出6个元素的。所以一定要复写equals方法的同时,一定要复写hashcode方法(当然,eclise有自动调用hashcode以及equals方法的,这里只不过是举例子罢了)

/*                                                                                   
 * 每个新建对象的hashcode不一样,所以要复写hashcode,这样的话这四个对象的hashcode就一样了                           
 * 因为hashcode的底层是hash码的                                                              
 * 只有hashcode一样的时候,才会调用equals方法,这样的话就少调用了很多次equals方法                                 
 * 所以要尽量是hashcode不一样                                                                 
 */                                                                                  
class Person {
    private String name;
    private int age;
    ...... 省略构造方法,set,get方法,toString方法......
    @Override                                                
    public boolean equals(Object obj){                       
       Person p = (Person)obj;                              
       return this.name.equals(p.name) && this.age == p.age;
   }
   @Override
   public int hashCode() {
      return 10;
   }                                                        
}
View Code

 只有hashcode方法一样时,才调用equals方法

一定要记得,要同时复写两个方法hashcode以及equals方法,set里面的对象集合才不会重复

扫描二维码关注公众号,回复: 5103397 查看本文章

!!!最新发现:因为hashset的底层是hashmap实现的,通过了解我们可以知道,hashmap的key值是先hashcode再取模,当这个下标一样时,才再计算有没有相等;

                 所以,以前死记的hashcode再equals,现在明白了吧

LinkedHashSet:

public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable

构造方法(都指向上面那个HashSet的第4个构造方法)

    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }
    
    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }
    
    public LinkedHashSet() {
        super(16, .75f, true);
    }
View Code

 SortedSet:

public interface SortedSet<E> extends Set<E>

As the name of the classsuggests, the sorting is accomplished by a tree data structure(The current implementation uses a red-black tree).

Every time an element is added to a tree, it is placed into its proper sorting position. Therefore, the iterator always visits the elements in sorted order.

Adding an element to a tree is slower than adding it to a hash table. But it is still much faster than checking for duplicates in an array or linked list.

TreeSet:

public class LinkedListTest {
    public static void main(String[] args) {
        Set<Person> ts = new TreeSet<Person>();
        ts.add(new Person("张三", 23));
        ts.add(new Person("李四", 13));
        ts.add(new Person("周七", 13));
        ts.add(new Person("王五", 43));
        ts.add(new Person("赵六", 33));
        System.out.println(ts);
    }
}

class Person implements Comparable<Person>{
    private String name;
    private int age;
    public Person(String name, int age) {
        this.age = age;
        this.name = name;
    }
    @Override
    public int compareTo(Person o) {
        return 0; // 返回值写死为0,元素值每次比较,都认为是相同的元素,这时就不再向TreeSet中插入除第一个外的新元素。所以TreeSet中就只存在插入的第一个元素。
        return -1; // 返回值写死为1,元素值每次比较,都认为新插入的元素比上一个元素大,于是二叉树存储时,会存在根的右侧,读取时就是正序排列的。
        return 1; // 返回值写死为-1,元素值每次比较,都认为新插入的元素比上一个元素小,于是二叉树存储时,会存在根的左侧,读取时就是倒序序排列的。
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}
View Code

所以要定义implements Comparable<Person>

class Person implements Comparable<Person>{
    private String name;
    private int age;
    public Person(String name, int age) {
        this.age = age;
        this.name = name;
    }
    @Override
    public int compareTo(Person o) {
        int num = this.age - o.age;                //年龄是比较的主要条件
        return num == 0 ? this.name.compareTo(o.name) : num;//姓名是比较的次要条件
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}
View Code

或者三层比较:

public int compareTo(Person o) {
        int length = this.name.length() - o.name.length();                //比较长度为主要条件
        int num = length == 0 ? this.name.compareTo(o.name) : length;    //比较内容为次要条件
        return num == 0 ? this.age - o.age : num;                        //比较年龄为次要条件
}
View Code

NavigableSet:

参考文档:

 1)JDK1.8源码(八)——java.util.HashSet 类

挨个傻瓜

猜你喜欢

转载自www.cnblogs.com/ericguoxiaofeng/p/8635632.html