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值
构造方法:从这些构造方法可以看出,初始化一个HashSet,实质上底层是初始化一个HashMap;最后一个是初始化LinkedHashMap,注意修饰符关键字,而且目前所知是为LinkedHashSet而服务的
所以说,hashset的这个成员变量 private transient HashMap<E,Object> map 前面三个构造方法的实现类是hashmap,后面那个的实现类是linkedhashmap,所以到时候put方法就是不一样了,一个是hashmap的put方法,一个是linkedhashmap的put方法
public HashSet() { map = new HashMap<>(); } public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } /* * 注意这个构造方法没有修饰符修饰,说明是这个方法只对所在的包可见 * dummy:ignored (distinguishes this constructor from other int, float constructor.) */ HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
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; }
其它方法是类似的,只要理解了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); } }
明显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; } }
复写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; } }
只有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
构造方法(都指向上面那个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); }
关于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这个方法()
其它方法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 + "]"; } }
所以要定义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 + "]"; } }
或者三层比较:
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; //比较年龄为次要条件 }
NavigableSet:
参考文档:
1)JDK1.8源码(八)——java.util.HashSet 类