Java API —— Set接口 & HashSet类 & LinkedHashSet类
1、Set接口
1)Set接口概述
一个不包含重复元素的 collection,无序(存储顺序和取出顺序不一致),唯一。 (List有序,即存储顺序和取出顺序一致,可重复)
2)Set案例
存储字符串并遍历
存储自定义对象并遍历
2、HashSet
1)HashSet类概述
不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。
2)HashSet如何保证元素唯一性
底层数据结构是哈希表(元素是链表的数组)
哈希表依赖于哈希值存储
添加功能底层依赖两个方法:
· int hashCode()
· boolean equals(Object obj)
例子1:存储字符串
package setdemos; import java.util.HashSet; import java.util.Set; /** * Created by gao on 15-12-17. */ public class HashSetDemo01 { public static void main(String[] args) { //创建集合对象 Set<String> set = new HashSet<String>(); //创建并添加元素 set.add("hello"); set.add("java"); set.add("world"); set.add("java"); set.add("android"); set.add("hello"); //增强for for(String s : set){ System.out.println(s); } } }
输出结果:(不存储重复的)
hello
android
java
world
例子2:存储自定义对象
学生类:重写hashCode()方法和equals()方法
package setdemos; /** * @author Administrator * */ public class Student { private String name; private int age; public Student() { super(); } public Student(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Student)) return false; Student student = (Student) o; if (age != student.age) return false; if (!name.equals(student.name)) return false; return true; } @Override public int hashCode() { int result = name.hashCode(); result = 31 * result + age; return result; } }
测试类:
package setdemos; import java.util.HashSet; /** * Created by gao on 15-12-17. */ /* * 需求:存储自定义对象,并保证元素的唯一性 * 要求:如果两个对象的成员变量值都相同,则为同一个元素。 * * 目前是不符合我的要求的:因为我们知道HashSet底层依赖的是hashCode()和equals()方法。 * 而这两个方法我们在学生类中没有重写,所以,默认使用的是Object类。 * 这个时候,他们的哈希值是不会一样的,根本就不会继续判断,执行了添加操作。 */ public class HashSetDemo02 { public static void main(String[] args) { // 创建集合对象 HashSet<Student> hs = new HashSet<Student>(); // 创建学生对象 Student s1 = new Student("林青霞", 27); Student s2 = new Student("柳岩", 22); Student s3 = new Student("王祖贤", 30); Student s4 = new Student("林青霞", 27); Student s5 = new Student("林青霞", 20); Student s6 = new Student("范冰冰", 22); // 添加元素 hs.add(s1); hs.add(s2); hs.add(s3); hs.add(s4); hs.add(s5); hs.add(s6); // 遍历集合 for (Student s : hs) { System.out.println(s.getName() + "---" + s.getAge()); } } }
输出结果:
王祖贤---30
范冰冰---22
林青霞---27
林青霞---20
柳岩---22
3)HashSet集合的add()方法的源码
问题:为什么存储字符串的时候,字符串内容相同的只存储了一个呢?
通过查看add方法的源码,我们知道这个方法底层依赖 两个方法:hashCode()和equals()。
步骤:
首先比较哈希值
如果相同,继续走,比较地址值或者走equals()
如果不同,就直接添加到集合中
按照方法的步骤来说:
先看hashCode()值是否相同
相同:继续走equals()方法
返回true: 说明元素重复,就不添加
返回false:说明元素不重复,就添加到集合
不同:就直接把元素添加到集合
如果类没有重写这两个方法,默认使用的Object()。一般来说不会相同。
而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉。只留下一个。
interface Collection { ... } interface Set extends Collection { ... } class HashSet implements Set { private static final Object PRESENT = new Object(); private transient HashMap<E,Object> map; public HashSet() { map = new HashMap<>(); } public boolean add(E e) { //e=hello,world return map.put(e, PRESENT)==null; } } class HashMap implements Map { public V put(K key, V value) { //key=e=hello,world //看哈希表是否为空,如果空,就开辟空间 if (table == EMPTY_TABLE) { inflateTable(threshold); } //判断对象是否为null if (key == null) return putForNullKey(value); int hash = hash(key); //和对象的hashCode()方法相关 //在哈希表中查找hash值 int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { //这次的e其实是第一次的world Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; //走这里其实是没有添加元素 } } modCount++; addEntry(hash, key, value, i); //把元素添加 return null; } transient int hashSeed = 0; final int hash(Object k) { //k=key=e=hello, int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); //这里调用的是对象的hashCode()方法 // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } } hs.add("hello"); hs.add("world"); hs.add("java"); hs.add("world");
4)HashSet存储元素保证唯一性的代码及图解
5)Set集合练习:
HashSet集合存储自定义对象并遍历。如果对象的成员变量值相同即为同一个对象
自定义类Dog类:
package hashsetdemos; /** * Created by gao on 15-12-17. */ public class Dog { private String name; private int age; private String color; private char sex; public Dog() { } public Dog(String name, int age, String color, char sex) { this.name = name; this.age = age; this.color = color; this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + ", color='" + color + '\'' + ", sex=" + sex + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Dog)) return false; Dog dog = (Dog) o; if (age != dog.age) return false; if (sex != dog.sex) return false; if (!color.equals(dog.color)) return false; if (!name.equals(dog.name)) return false; return true; } @Override public int hashCode() { int result = name.hashCode(); result = 31 * result + age; result = 31 * result + color.hashCode(); result = 31 * result + (int) sex; return result; } }
测试类:
package hashsetdemos; import java.util.HashSet; /** * Created by gao on 15-12-17. */ /* * HashSet集合存储自定义对象并遍历。如果对象的成员变量值相同即为同一个对象 * * 注意了: * 你使用的是HashSet集合,这个集合的底层是哈希表结构。 * 而哈希表结构底层依赖:hashCode()和equals()方法。 * 如果你认为对象的成员变量值相同即为同一个对象的话,你就应该重写这两个方法。 * 如何重写呢?不同担心,自动生成即可。 */ public class Exercise01 { public static void main(String[] args) { // 创建集合对象 HashSet<Dog> hs = new HashSet<Dog>(); // 创建狗对象 Dog d1 = new Dog("秦桧", 25, "红色", '男'); Dog d2 = new Dog("高俅", 22, "黑色", '女'); Dog d3 = new Dog("秦桧", 25, "红色", '男'); Dog d4 = new Dog("秦桧", 20, "红色", '女'); Dog d5 = new Dog("魏忠贤", 28, "白色", '男'); Dog d6 = new Dog("李莲英", 23, "黄色", '女'); Dog d7 = new Dog("李莲英", 23, "黄色", '女'); Dog d8 = new Dog("李莲英", 23, "黄色", '男'); //添加元素 hs.add(d1); hs.add(d2); hs.add(d3); hs.add(d4); hs.add(d5); hs.add(d6); hs.add(d7); hs.add(d8); //遍历 for (Dog d : hs) { System.out.println(d.getName() + "---" + d.getAge() + "---" + d.getColor() + "---" + d.getSex()); } } }
输出结果:
李莲英---23---黄色---男
魏忠贤---28---白色---男
李莲英---23---黄色---女
秦桧---25---红色---男
秦桧---20---红色---女
高俅---22---黑色---女
3、LinkedHashSet类
具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。注意,插入顺序不 受在 set 中重新插入的 元素的影响。
LinkedHashSet:底层数据结构由哈希表和链表组成。
哈希表保证元素的唯一性。
链表保证元素有素。(存储和取出是一致)
package linkedhashset; import java.util.LinkedHashSet; /** * Created by gao on 15-12-17. */ public class LinkedHashSetDemo { public static void main(String[] args) { // 创建集合对象 LinkedHashSet<String> hs = new LinkedHashSet<String>(); // 创建并添加元素 hs.add("hello"); hs.add("world"); hs.add("java"); hs.add("world"); hs.add("java"); // 遍历 for (String s : hs) { System.out.println(s); } } }