Java API —— Set接口 & HashSet类 & LinkedHashSet类

时间:2021-11-29 15:33:52
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存储元素保证唯一性的代码及图解

 Java API —— Set接口 & HashSet类 & LinkedHashSet类

 

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