HashSet、LinkedHashSet、SortedSet、TreeSet

HashSet概况:

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

HashSet源码:

常量:

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

构造方法:

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

 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;
}                                    

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方法

/*                                                                                   
 * 每个新建对象的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里面的对象集合才不会重复

 

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

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

 

LinkedHashSet:

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

构造方法

 

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

 

 

 

 关于add()方法

HashSet<Integer> set = new LinkedHashSet<>();
Set<Integer> set02 = new LinkedHashSet<>();
set.add(1);
set02.add(1);
1,首先调用子类LinkedHashSet的add方法,发现没有
2,然后调用上一层父类HashSet的add方法,发现有了
public boolean add(E e) {
    return map.put(e, PRESENT)==null;  // 注意:这里的map的实现类为LinkedHashMap
}
注意:
1)无论接收者是什么,一定要是你老豆,或者老豆的老豆啊,或者更老豆
2)但是接收者,也就是老豆,一定要定义这个方法才行啊:
最典型:    LinkedList<String> list = new LinkedList<>();   list.addFirst("a");    
不能  List<String> list = new LinkedList<>();     因为List接口没有定义addFirst这个方法()
View Code

其它方法remove,contains也类似

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 类

 

posted @ 2019-01-28 14:53  天马行空郭  阅读(888)  评论(0编辑  收藏  举报