一:Set系列的集合:
无序:存取顺序不一致
import java.util.HashSet;
import java.util.Set;
public class TraverString {
public static void main(String[] args) {
//创建一个Hashset对象
Set<String> s = new HashSet<>();
s.add("A");
s.add("A");
s.add("B");
s.add("C");
//打印出来后没有重复数据
System.out.println(s);
}
}
不重复:去除重复数据
import java.util.HashSet;
import java.util.Set;
public class TraverString {
public static void main(String[] args) {
//创建一个Hashset对象
Set<String> s = new HashSet<>();
//使用两个boolean变量记录数据是否添加进入了Hashset中
boolean r1 = s.add("A");
boolean r2 = s.add("A");
//r1为true,r2为false因为在Set系列中有着不重复的特点,会将重复数据去除
System.out.println(r1);
System.out.println(r2);
//自然最后打印出来的数据也就只有一个[A]
System.out.println(s);
}
}
无索引:没有带索引的方法,所以不能使用普通循环遍历,也不能通过索引来获取元素
二:Set集合的实现类
Hashset:无序,不重复,无索引
linkedHashSet:有序,不重复,无索引
TreSet:可排序,不重复,无索引
Set接口中的方法基本上与Collection的API一致
Collectionn:
Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的
其中的一些方法:
public boolean add(E e)把给定的对象添加到当前集合中
public void clear() 清空集合中所有的元素
public boolean remove(E e)把给定的对象在当前集合中删除
public boolean contains(Object obj)判断当前集合中是否包含给定的对象
public boolean isEmpty()判断当前集合是否为空
public int size()返回集合中元素的个数/集合的长度
三:存储字符串并遍历
利用Set系列的集合,添加字符串,并使用多种方式遍历
(1):迭代器
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class TraverString {
public static void main(String[] args) {
//创建一个Hashset对象
Set<String> s = new HashSet<>();
s.add("A");
s.add("A");
s.add("B");
s.add("C");
Iterator<String> it = s.iterator();
while (it.hasNext()) {
String str = it.next();
System.out.println(str);
}
}
}
(2):增强for
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class TraverString {
public static void main(String[] args) {
//创建一个Hashset对象
Set<String> s = new HashSet<>();
s.add("A");
s.add("A");
s.add("B");
s.add("C");
for (String str : s) {
System.out.println(str);
}
}
}
(3):Lambda表达式
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Consumer;
public class TraverString {
public static void main(String[] args) {
//创建一个Hashset对象
Set<String> s = new HashSet<>();
s.add("A");
s.add("A");
s.add("B");
s.add("C");
s.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
}
}
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Consumer;
public class TraverString {
public static void main(String[] args) {
//创建一个Hashset对象
Set<String> s = new HashSet<>();
s.add("A");
s.add("A");
s.add("B");
s.add("C");
s.forEach(str -> System.out.println(str));
}
}
Set集合的实现类特点:
HashSet:无序,不重复,无索引
LinkedHashSet:有序,不重复,无索引
TreeSet:可排序,不重复,无索引
四:HashSet
哈希值:
1.根据hashCode方法算出来的int类型的整数
2.该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
3.一般情况下会重写hashCode方法,利用对象内部的属性值计算哈希值
对象的哈希值特点:
1.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
hashCode:
public class hashCode {
public static void main(String[] args) {
Student s1 = new Student("A", 18);
Student s2 = new Student("A", 18);
boolean judge = (s1.hashCode() == s2.hashCode());
System.out.println(judge);
}
}
Student:
public class Student {
String name;
int age;
public Student(){
}
public Student(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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2.如果已经重写hashCode方法,不同对象只要属性值相同,计算出的哈希值就是一样的
hashCode:
public class hashCode {
public static void main(String[] args) {
Student s1 = new Student("A", 18);
Student s2 = new Student("A", 18);
boolean judge = (s1.hashCode() == s2.hashCode());
System.out.println(judge);
}
}
Student:
import java.util.Objects;
public class Student {
String name;
int age;
public Student(){
}
public Student(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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student student)) return false;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
3.在小部分情况发生了哈希碰撞,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希值的数据一共有42亿多个,那么如果我们创建超过数据个数的对象个数那么就必定会出现哈希碰撞,当然就算不这么做也会出现很多这种情况了)
HashSet底层原理:
1.HashSet集合底层采取哈希表存储数据
2.哈希表是一种对于增删改查数据性能都较好的结构
哈希表组成:
1.JDK8之前:数组+链表
2.JDK8开始:数组+链表+红黑树
哈希表的创建:
1.创建一个默认长度16,默认加载因子为0.75的数组
(加载因子的作用:当数组中存了数组长度*加载因子个元素的时候(如上举例的16*0.75=12个元素的时候),这时就会对数组进行扩容,大小变为原先的两倍)
2.根据元素的哈希值跟数组的长度计算出应存入的位置
计算公式:int index=(数组长度-1)&哈希值
3.判断当前位置是否为null,如果是null就直接存入
4.如果位置不为null,表示有元素,则调用equals方法比较属性值
5.属性值相同:不存入
属性值不同:存入数组形成链表
JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8以后:新元素直接挂在老元素下面
6.链表相关的注意事项
(1).如果在一个索引下已经形成链表,此时要再加入新元素的话就要使用equals方法与链表中的所有元素都进行比较,只有全都不一样时才加入这个链表
(2)。当链表长度大于8且数组长度大于等于64时,就会从链表转化为红黑树以提高查找效率
HashSet的三个问题:
1.HashSet为什么存和取的顺序不一样?
HashSet的存入机制在上方已经提到,那么现在就要说到HashSet中的取出元素操作了
在HashSet中是根据索引在数组中顺序查找取出数据的,同时如果已经形成链表那么就会依次将链表中的数据取出,然后再到下一个索引中,因此存入的顺序和顺序查找数组并输出数据的顺序自然而然是不同的,也就会出现存取不相同的情况
2.HashSet为什么没有索引?
在HashSet中同时利用了链表与数组两种数据结构,甚至可以看作把一个链表放在了数组的一个格中,那么如果要使用索引的话同一个链表的不同元素要怎么办呢?同为一个索引吗?所以取消了索引机制。
3.HashSet是利用什么机制保证数据去重的?
使用HashCode方法获取哈希值,确定当前数据的位置,然后使用equals方法比较对象的属性值,确保数据不同
利用HashSet集合去除重复元素
需求:创建一个存储学生对象的集合,存储多个学生对象,然后遍历该集合
要求:学生对象的成员变量值相同时我们就认为是同一个对象
Student:
import java.util.Objects;
public class Student {
String name;
int age;
public Student(){
}
public Student(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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student student)) return false;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
hashCode:
import java.util.HashSet;
public class hashCode {
public static void main(String[] args) {
Student s1 = new Student("A", 18);
Student s2 = new Student("A", 18);
Student s3 = new Student("B", 19);
Student s4 = new Student("C", 20);
HashSet<Student> hashSet = new HashSet<>();
hashSet.add(s1);
hashSet.add(s2);
hashSet.add(s3);
hashSet.add(s4);
System.out.println(hashSet);
}
}
五:LinkedHashSet:
特性:有序,不重复,无索引
有序:保证存储和取出的元素顺序一致
虽然LinkedHashSet的底层数据结构依然是哈希表,但是每个元素又额外的多了一个双链表的机制记录存储的顺序,最后会依据双向链表所记录的存储的顺序输出元素
Student:
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student(){
}
public Student(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;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student student)) return false;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Linkedhashset:
import java.util.LinkedHashSet;
public class Linkedhashset {
public static void main(String[] args) {
Student s1=new Student("A",18);
Student s2=new Student("A",18);
Student s3=new Student("B",19);
Student s4=new Student("C",20);
LinkedHashSet<Student> linkedHashSet=new LinkedHashSet<>();
linkedHashSet.add(s1);
linkedHashSet.add(s2);
linkedHashSet.add(s3);
linkedHashSet.add(s4);
System.out.println(linkedHashSet);
}
}
以上就是有关LinkedHashSet和HashSet的全部内容,那么在以后如果要数据去重,我们使用哪个呢?
1.默认使用HashSet
2.如果要求去重且存储有序,那么使用LinkedHashSet