文章目录
1:集合体系图
Collection:单列集合,该接口有两个重要的子接口List Set,他们的实现子类都是单列集合
Map:双列集合,Map接口的实现子类,存放K-V
2:Set接口和常用方法
Set接口基本介绍
1)无序(添加和取出的顺序不一致)没有索引(没有get方法,所以也没有普通for循环遍历)
2) 不允许重复元素,所以最多包含一个null
3) JDK API中Set接口的实现类有:
Set接口的常用方法
和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样.
Set接口的遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
1.可以使用迭代器
2.增强for
3.不能使用索引的方式来获取.
案例演示
public class SetMethod {
public static void main(String[] args) {
//注意:取出的顺序虽然不是添加的顺序,但是它是固定的
Set hashSet = new HashSet();
hashSet.add("zlj");
hashSet.add("zlj");
hashSet.add("zzz");
hashSet.add(null);
hashSet.add(null);
System.out.println("hashSet="+hashSet);
System.out.println("=======Iterator遍历==========");
Iterator iterator = hashSet.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("hashSet="+next);
}
System.out.println("=======增强for循环遍历==========");
for (Object object:hashSet){
System.out.println("hashSet="+object);
}
}
}
hashSet=[null, zlj, zzz]
=======Iterator遍历==========
hashSet=null
hashSet=zlj
hashSet=zzz
=======增强for循环遍历==========
hashSet=null
hashSet=zlj
hashSet=zzz
3:HashSet全面说明
-
HashSet实现了Set接口
-
HashSet实际上是HashMap
public HashSet() {
map = new HashMap<>();
}
3)可以存放nul值,但是只能有一个null
- HashSet不保证元素是有序的,取决于hash后,再确定索引的结果。(即不保证存放元素的顺序,和取出的顺序一致)
5)不能有重复元素/对象在前面Set接口使用已经讲过
我们先来理解一下什么是重复元素/对象,看如下的例子
public class HashSet01 {
public static void main(String[] args) {
Set set=new HashSet();
System.out.println(set.add(1));//添加成功
System.out.println(set.add(1));//添加失败
System.out.println(set.add(new Dog("zzz")));//添加成功
System.out.println(set.add(new Dog("zzz")));//添加成功
//经典面试题,为什么第二个new String就是添加失败,我们就必须要去看它的源码
System.out.println(set.add(new String("zzz")));//添加成功
System.out.println(set.add(new String("zzz")));//添加失败
}
}
true
false
true
true
true
false
为什么第二个new String就是添加失败?到底什么叫做重复元素/对象呢?这就需要我们去观察HashSet的源码,来进行分析
3.1 HashSet底层机制说明
HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)
我们现在就使用Java模拟一下HashSet底层,数组+链表,模拟上面的图
public class HashSetStructure {
public static void main(String[] args) {
//模拟一个HashSet的底层(HashMap底层结构)
//1:创建一个数组,数组的类型是Node[]
//2:有些人,直接把Node[]数组称为表
Node[] nodes = new Node[16];
System.out.println("table="+nodes);
//3:创建节点john存入到 nodes[2]
Node john = new Node("john", null);
nodes[2]=john;
Node jack = new Node("jack", null);
john.next=jack;//将jack节点挂载到john
Node rose = new Node("Rose", null);
jack.next=rose;//将rose节点挂载到jack
//3:创建节点lucy 存入到 nodes[3]
Node lucy = new Node("Lucy", null);
nodes[3]=lucy;
System.out.println("table="+nodes);
}
}
class Node{//节点,存储数据,可以指向下一个节点,从而形成链表
Object item;//存放数据
Node next;//指向下一个节点
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
使用debug,john成功放在数组2中,Lucuy成功放在数组3中,并且john后存放jack,Rose
分析HashSet的添加元素底层是如何实现(hash()+equals())
源码解读
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("java");//第一个方法
hashSet.add("php");//第二个方法
hashSet.add("java");//第三个方法
System.out.println("set="+hashSet);
}
}
3.2 HashSet底层机制——扩容和转成红黑树机制再说明
先说结论,在进行代码演示
演示HashSet的扩容机制
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);
}
}
}
演示HashSet的转成红黑树机制
代码演示
public class HashSetIncrement {
public static void main(String[] args) {
/**
*/
HashSet hashSet=new HashSet();
for (int i=1;i<100;i++){
// hashSet.add(i);
hashSet.add(new A(i));
}
System.out.println("Hashset="+hashSet);
}
}
class A{
private int i;
public A(int i) {
this.i = i;
}
@Override//为了保证hash一样,但是键不一样,就可以放在同一个数组索引中的不同链表上去
public int hashCode(){
return 100;
}
}
4:HashSet实战
定义一个Employee类,该类包含: private成员属性name,age
要求:
1.创建3个Employee对象放入HashSet中
2.当name和age的值相同时,认为是相同员工,不能添加到HashSet集合中
代码演示
public class HashSetExercise {
public static void main(String[] args) {
Set set=new HashSet();
set.add(new Employee("zlj",22));
set.add(new Employee("zlj",22));
set.add(new Employee("zl",21));
System.out.println("Hashset="+set);
}
}
class Employee{
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
//重写equals和hashCode,年龄,名字都相同,比较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);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Hashset=[Employee{name='zl', age=21}, Employee{name='zlj', age=22}]
5:LinkedHashSet介绍
-
LinkedHashSet是HashSet的子类
-
LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
-
LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
-
LinkedHashSet不允许添重复元素