1、Set 接口和常用方法
(1)Set 接口基本介绍
① 无序(添加和去除的顺序不一致),没有索引;
② 不允许重复元素,所以最多包含一个null;
(2)Set 接口的常用方法
和 List 接口一样, Set 接口也是 Collection 的子接口,因此,常用方法和 Collection 接口一样。
(3)Set 接口的遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
① 可以使用迭代器;
② 增强for;
③ 不能使用索引的方式来获取。
(4)Set接口的常用方法举例
@SuppressWarnings({"all"}) public class SetMethod { public static void main(String[] args) { //解读 //1. 以Set 接口的实现类 HashSet 来讲解Set 接口的方法 //2. set 接口的实现类的对象(Set接口对象), 不能存放重复的元素, 可以添加一个null //3. set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致) //4. 注意:取出的顺序的顺序虽然不是添加的顺序,但是他的固定. Set set = new HashSet(); set.add("john"); set.add("lucy"); set.add("john");//重复 set.add("jack"); set.add("hsp"); set.add("mary"); set.add(null);// set.add(null);//再次添加null for(int i = 0; i <10;i ++) { System.out.println("set=" + set); } //遍历 //方式1: 使用迭代器 System.out.println("=====使用迭代器===="); Iterator iterator = set.iterator(); while (iterator.hasNext()) { Object obj = iterator.next(); System.out.println("obj=" + obj); } set.remove(null); //方式2: 增强for System.out.println("=====增强for===="); for (Object o : set) { System.out.println("o=" + o); } //set 接口对象,不能通过索引来获取 } }
2、Set 接口实现类-HashSet
(1)HashSet的全面说明
① HashSet实现了Set接口;
② HashSet实际上是HashMap;
③ 可以存放null值,但是只能有一个null;
④ HashSet不保证元素是有序的,取决于hash后,再确定索引的结果;(即,不保证存放元素的顺序和取出顺序一致)
⑤ 不能有重复元素/对象。
(2)HashSet案例说明
@SuppressWarnings({"all"}) public class HashSet01 { public static void main(String[] args) { HashSet set = new HashSet(); //说明 //1. 在执行add方法后,会返回一个boolean值 //2. 如果添加成功,返回 true, 否则返回false //3. 可以通过 remove 指定删除哪个对象 System.out.println(set.add("john"));//T System.out.println(set.add("lucy"));//T System.out.println(set.add("john"));//F System.out.println(set.add("jack"));//T System.out.println(set.add("Rose"));//T set.remove("john"); System.out.println("set=" + set);//3个 // set = new HashSet(); System.out.println("set=" + set);//0 //4 Hashset 不能添加相同的元素/数据? set.add("lucy");//添加成功 set.add("lucy");//加入不了 set.add(new Dog("tom"));//OK set.add(new Dog("tom"));//Ok System.out.println("set=" + set); //在加深一下. 非常经典的面试题. //看源码,做分析, 先给小伙伴留一个坑,以后讲完源码,你就了然 //去看他的源码,即 add 到底发生了什么?=> 底层机制. set.add(new String("hsp"));//ok set.add(new String("hsp"));//加入不了. System.out.println("set=" + set); } } class Dog { //定义了Dog类 private String name; public Dog(String name) { this.name = name; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + '}'; } }
@SuppressWarnings({"all"}) public class HashSetStructure { public static void main(String[] args) { //模拟一个HashSet的底层 (HashMap 的底层结构) //1. 创建一个数组,数组的类型是 Node[] //2. 有些人,直接把 Node[] 数组称为 表 Node[] table = new Node[16]; //3. 创建结点 Node john = new Node("john", null); table[2] = john; Node jack = new Node("jack", null); john.next = jack;// 将jack 结点挂载到john Node rose = new Node("Rose", null); jack.next = rose;// 将rose 结点挂载到jack Node lucy = new Node("lucy", null); table[3] = lucy; // 把lucy 放到 table表的索引为3的位置. System.out.println("table=" + table); } } class Node { //结点, 存储数据, 可以指向下一个结点,从而形成链表 Object item; //存放数据 Node next; // 指向下一个结点 public Node(Object item, Node next) { this.item = item; this.next = next; } }
(3)HashSet底层
@SuppressWarnings({"all"}) public class HashSetSource { public static void main(String[] args) { HashSet hashSet = new HashSet(); hashSet.add("java");//到此位置,第1次add分析完毕. hashSet.add("php");//到此位置,第2次add分析完毕 hashSet.add("java"); System.out.println("set=" + hashSet); /* 对HashSet 的源码解读 1. 执行 HashSet() public HashSet() { map = new HashMap<>(); } 2. 执行 add() public boolean add(E e) {//e = "java" return map.put(e, PRESENT)==null;//(static) PRESENT = new Object(); } 3.执行 put() , 该方法会执行 hash(key) 得到key对应的hash值 算法h = key.hashCode()) ^ (h >>> 16) public V put(K key, V value) {//key = "java" value = PRESENT 共享 return putVal(hash(key), key, value, false, true); } 4.执行 putVal final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量 //table 就是 HashMap 的一个数组,类型是 Node[] //if 语句表示如果当前table 是null, 或者 大小=0 //就是第一次扩容,到16个空间. if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //(1)根据key,得到hash 去计算该key应该存放到table表的哪个索引位置 //并把这个位置的对象,赋给 p //(2)判断p 是否为null //(2.1) 如果p 为null, 表示还没有存放元素, 就创建一个Node (key="java",value=PRESENT) //(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null) if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { //一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建 Node<K,V> e; K k; // //如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样 //并且满足 下面两个条件之一: //(1) 准备加入的key 和 p 指向的Node 结点的 key 是同一个对象 //(2) p 指向的Node 结点的 key 的equals() 和准备加入的key比较后相同 //就不能加入 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //再判断 p 是不是一颗红黑树, //如果是一颗红黑树,就调用 putTreeVal , 来进行添加 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {//如果table对应索引位置,已经是一个链表, 就使用for循环比较 //(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后 // 注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点 // , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树) // 注意,在转成红黑树时,要进行判断, 判断条件 // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64)) // resize(); // 如果上面条件成立,先table扩容. // 只有上面条件不成立时,才进行转成红黑树 //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; //size 就是我们每加入一个结点Node(k,v,h,next), size++ if (++size > threshold) resize();//扩容 afterNodeInsertion(evict); return null; } */ } }
(4)HashSet 课堂练习
@SuppressWarnings({"all"}) public class HashSetExercise { public static void main(String[] args) { /** 定义一个Employee类,该类包含:private成员属性name,age 要求: 创建3个Employee 对象放入 HashSet中 当 name和age的值相同时,认为是相同员工, 不能添加到HashSet集合中 */ HashSet hashSet = new HashSet(); hashSet.add(new Employee("milan", 18));//ok hashSet.add(new Employee("smith", 28));//ok hashSet.add(new Employee("milan", 18));//加入不成功. //回答,加入了几个? 3个 System.out.println("hashSet=" + hashSet); } } //创建Employee class Employee { private String name; private int age; public Employee(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", age=" + age + '}'; } public void setAge(int age) { this.age = age; } //如果name 和 age 值相同,则返回相同的hash值 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return age == employee.age && Objects.equals(name, employee.name); } @Override public int hashCode() { return Objects.hash(name, age); } }
@SuppressWarnings({"all"}) public class HashSetPractice01 { public static void main(String[] args) { HashSet hashSet = new HashSet(); hashSet.add(new Employee01("小李",30000,new MyDate(1999,6,14))); hashSet.add(new Employee01("小李",30000,new MyDate(1999,6,14))); for (Object o :hashSet) { System.out.println(o); } } } @SuppressWarnings({"all"}) class Employee01{ private String name; private double sal; private MyDate birthday; public Employee01(String name, double sal, MyDate birthday) { this.name = name; this.sal = sal; this.birthday = birthday; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSal() { return sal; } public void setSal(double sal) { this.sal = sal; } public MyDate getBirthday() { return birthday; } public void setBirthday(MyDate birthday) { this.birthday = birthday; } @Override public String toString() { return "Employee01{" + "name='" + name + '\'' + ", sal=" + sal + ", birthday=" + birthday + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee01 that = (Employee01) o; return Objects.equals(name, that.name) && Objects.equals(birthday, that.birthday); } @Override public int hashCode() { return Objects.hash(name, birthday); } } @SuppressWarnings({"all"}) class MyDate{ private int year; private int month; private int day; public MyDate(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } @Override public String toString() { return "MyDate{" + "year=" + year + ", month=" + month + ", day=" + day + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyDate myDate = (MyDate) o; return year == myDate.year && month == myDate.month && day == myDate.day; } @Override public int hashCode() { return Objects.hash(year, month, day); } }
3、Set 接口实现类-LinkedHashSet
(1)LinkedHashSet 的全面说明
① LinkedHashSet 是 HashSet 的子类;
② LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组+双向链表;
③ LinkedHashSet 根据元素 hashCode来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的;
④ LinkedHashSet 不允许添加重复元素。
@SuppressWarnings({"all"}) public class LinkedHashSetSource { public static void main(String[] args) { //分析一下LinkedHashSet的底层机制 Set set = new LinkedHashSet(); set.add(new String("AA")); set.add(456); set.add(456); set.add(new Customer("刘", 1001)); set.add(123); set.add("HSP"); System.out.println("set=" + set); //解读 //1. LinkedHashSet 加入顺序和取出元素/数据的顺序一致 //2. LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类) //3. LinkedHashSet 底层结构 (数组table+双向链表) //4. 添加第一次时,直接将 数组table 扩容到 16 ,存放的结点类型是 LinkedHashMap$Entry //5. 数组是 HashMap$Node[] 存放的元素/数据是 LinkedHashMap$Entry类型 /* //继承关系是在内部类完成. static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } */ } } class Customer { private String name; private int no; public Customer(String name, int no) { this.name = name; this.no = no; } }
(2)LinkedHashSet练习题
@SuppressWarnings({"all"}) public class LinkedHashSetExercise { public static void main(String[] args) { LinkedHashSet linkedHashSet = new LinkedHashSet(); linkedHashSet.add(new Car("奥拓", 1000));//OK linkedHashSet.add(new Car("奥迪", 300000));//OK linkedHashSet.add(new Car("法拉利", 10000000));//OK linkedHashSet.add(new Car("奥迪", 300000));//加入不了 linkedHashSet.add(new Car("保时捷", 70000000));//OK linkedHashSet.add(new Car("奥迪", 300000));//加入不了 System.out.println("linkedHashSet=" + linkedHashSet); } } /** * Car 类(属性:name,price), 如果 name 和 price 一样, * 则认为是相同元素,就不能添加。 5min */ class Car { private String name; private double price; public Car(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } @Override public String toString() { return "\nCar{" + "name='" + name + '\'' + ", price=" + price + '}'; } //重写equals 方法 和 hashCode //当 name 和 price 相同时, 就返回相同的 hashCode 值, equals返回t @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Car car = (Car) o; return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name); } @Override public int hashCode() { return Objects.hash(name, price); } }