Set接口,
- 接本介绍
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重负元素,所以最多包含一个null
- Set接口的常用方法
- 和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection一样
- Set接口的遍历方式
- 同Collection的遍历方式一样,因为Set接口是Collection接口的子接口
- 可以使用迭代器
- 增强for
- 不能使用索引的方式来获取
-
package com.jshedu.Set_;import java.util.HashSet; import java.util.Iterator; import java.util.Set;/* @author 韩顺平* @version 1.0*/ @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);//再次添加nullfor(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: 增强forSystem.out.println("=====增强for====");for (Object o : set) {System.out.println("o=" + o);}//set 接口对象,不能通过索引来获取} }
遍历只有两种方法
-
package com.jshedu.Set_;import java.util.HashSet; import java.util.Set;/* @author 韩顺平* @version 1.0*/ @SuppressWarnings({"all"}) public class HashSet_ {public static void main(String[] args) {//老韩解读//1. 构造器走的源码/*public HashSet() {map = new HashMap<>();//实际上是HashMap}2. HashSet 可以存放null ,但是只能有一个null,即元素或对象不能重复3.HashSet不保证元素是有序的,取决于hash后,在确定索引的结果(不保证存放顺序和取出顺序一样)*/Set hashSet = new HashSet();hashSet.add(null);hashSet.add(null);System.out.println("hashSet=" + hashSet);} }
HashSet讲解
-
HashSet底层原理
-
package com.jshedu.Set_;/* @author 韩顺平* @version 1.0*/ @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;//在链表中,分两步//1.先创建要加入的结点//2.用属性next,把结点的地址赋给它。就把结点连起来了Node jack = new Node("jack", null);john.next = jack;// 将jack 结点挂载到johnNode rose = new Node("Rose", null);jack.next = rose;// 将rose 结点挂载到jackNode 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;} }
在数组的指定位置添加链表形式的数据。
-
-
HashSet添加元素底层是如何实现的
-
HashSet底层是HashMap
-
添加一个元素时,先得到hash值-->会转成--->索引值
-
找到存储数据表table,看这个索引位置是否已经存放的有元素
-
如果没有,直接加入
-
如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
-
在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)。【如果table的大小还没有到64,但链表的个数超过8,那么table就会扩容,2倍扩容】
-
package com.jshedu.Set_;import java.util.HashSet;/* @author 韩顺平* @version 1.0*/ @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)hash值不是hashCode,是经过处理的hashCode。右移16位public V put(K key, V value) {//key = "java" value = PRESENT 共享return putVal(hash(key), key, value, false, true);}4.执行 putValfinal 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)//i = (n - 1) & hash,决定元素放到那个位置if ((p = tab[i = (n - 1) & hash]) == null)//p指向表的一个结点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扩容。(就是虽然你的链表有8个元素,但数组还没有到64个)// 只有上面条件不成立时,才进行转成红黑树//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接breakfor (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1sttreeifyBin(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 keyV 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;}*/} }
add
-
-
HashSet的扩容和转成红黑树机制
-
当一个链表上元素个数是8之后再在该链表上添加元素,会导致table扩容,按扩容机制来扩容,而且链表的hash值决定的table数组下标也会发生改变。链表上在添加,table在扩容,当table大于64的时候,链表会被树化,不在扩容。
-
package com.jshedu.Set_;import java.util.HashSet; import java.util.Objects;/* @author 韩顺平* @version 1.0*/ @SuppressWarnings({"all"}) public class HashSetIncrement {public static void main(String[] args) {/*HashSet底层是HashMap, 第一次添加时,table 数组扩容到 16,临界值(threshold)是 16*加载因子(loadFactor)是0.75 = 12如果table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32,新的临界值就是 32*0.75 = 24, 依次类推*/HashSet hashSet = new HashSet(); // for(int i = 1; i <= 100; i++) { // hashSet.add(i);//1,2,3,4,5...100 // }/*在Java8中, 如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8 ),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制*/// for(int i = 1; i <= 12; i++) { // hashSet.add(new A(i));// // }/*当我们向hashset增加一个元素,-> Node -> 加入table , 就算是增加了一个size++size和扩容有关(不是table达到0.75才扩容,只要链表元素的个数达到0.75也扩容不管table上有几个链表)*/for(int i = 1; i <= 7; i++) {//在table的某一条链表上添加了 7个A对象hashSet.add(new A(i));//}for(int i = 1; i <= 7; i++) {//在table的另外一条链表上添加了 7个B对象hashSet.add(new B(i));//}} }class B {private int n;public B(int n) {this.n = n;}@Overridepublic int hashCode() {return 200;} }class A {private int n;public A(int n) {this.n = n;}@Overridepublic int hashCode() {return 100;} }
hashCode值是怎么算出来的不用管,内部有一个数学算法,尽量不同的Object数组得到的hashCode不同。
-
-
例题
-
package com.jshedu.Set_;import java.util.HashSet; import java.util.Objects;/* @author Mr.jia* @version 1.0*/public class Homework01 {@SuppressWarnings({"all"})public static void main(String[] args) {HashSet obj = new HashSet();obj.add(new Employee("jack",20));//在add过程中就用hashCode()方法进行比较name,ageobj.add(new Employee("tom",22));obj.add(new Employee("tom",32));obj.add(new Employee("jack",20));//加不进去//重写hashCode(),equals()前//这里加入四个对象,为什么是四个,第四个和第一个不是一样吗//答:创建四个对象,返回的hash值不一样,所以在table表中// 存储的位置也就不同//重写后会对name,age进行比较System.out.println(obj.toString());} } 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;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Employee{" +"name='" + name + '\\'' +", age=" + age +'}';}@Overridepublic 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);}//name和age相同就返回相同的hash值@Overridepublic int hashCode() {return Objects.hash(name, age);//先比较返回hash值是否相同,相同在比较内容,//内容不同就直接放链表尾部//相同就不加入} }
重写hashCode(),和equals()
-
package com.jshedu.Set_;import java.util.HashSet; import java.util.Objects;/* @author Mr.jia* @version 1.0*/public class Homework02 {public static void main(String[] args) {HashSet<Object> objects = new HashSet<>();MyDate myDate = new MyDate(1999, 4, 17);objects.add(new Employer("jack",10000,myDate));MyDate myDate1 = new MyDate(1999, 5, 17);objects.add(new Employer("tom",10000,myDate));objects.add(new Employer("jack",10000,myDate));for (Object o :objects) {System.out.println("Employee:"+o);}//System.out.println(objects);} } class Employer{private String name;private double sal;private MyDate birthday; // class MyDate1{ // private int year; // private int month; // private int day; // }public Employer(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;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Employer employer = (Employer) o;return Objects.equals(name, employer.name) && Objects.equals(birthday, employer.birthday);}@Overridepublic int hashCode() {return Objects.hash(name, birthday);}@Overridepublic String toString() {return "Employer{" +"name='" + name + '\\'' +", sal=" + sal +", birthday=" + birthday +'}';} } 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;} }
参数是引用类型怎么处理myDate
-