Set
Set集合概述和特点
Set集合特点
- 不包含重复元素的集合
- 没有带索引的方法,所以不能使用普通for循环遍历
Set集合练习
- 存储字符串并遍历
import java.util.HashSet;
import java.util.Set;
/*
Set集合特点
不包含重复元素的集合
没有带索引的方法,所以不能使用普通for循环遍历
HashSet:对集合的迭代顺序不作任何保证
*/
public class SetDemo {
public static void main(String[] args) {
//创建集合对象
//HashSet:对集合的迭代顺序不作任何保证
Set<String> set = new HashSet<String>();
//添加元素
set.add("hello");
set.add("world");
set.add("java");
//不包含重复元素
set.add("hello");
//遍历
for (String s : set) {
System.out.println(s);
}
/*运行结果:
world
java
hello
*/
}
}
运行结果:
哈希值
哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中有一个方法可以获取对象的哈希值
- public int hashCode():返回对象的哈希码值
对象的哈希值特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
/*
哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中有一个方法可以获取对象的哈希值:
public int hashCode():返回对象的哈希码值
*/
public class HashDemo {
public static void main(String[] args) {
//创建学生对象
Student s1 = new Student("小白", 12);
//同一个对象多次调用hashCode()方法返回的哈希值是相同的
System.out.println(s1.hashCode());//1854778591
System.out.println(s1.hashCode());//1854778591
System.out.println("--------");
//创建学生对象
Student s2 = new Student("小白", 22);
//默认情况下,不同对象的哈希值是不相同的
//通过方法重写,可以实现不同对象的哈希值相同
System.out.println(s2.hashCode());
System.out.println("--------");
System.out.println("hello".hashCode());//99162322
System.out.println("world".hashCode());//113318802
System.out.println("java".hashCode());//3254818
System.out.println("world".hashCode());//113318802
System.out.println("--------");
System.out.println("重地".hashCode());//1179395
System.out.println("通话".hashCode());//1179395
System.out.println("程序".hashCode());//992740
System.out.println("代码".hashCode());//656766
}
}
运行结果:
HashSet集合概述和特点
HashSet集合特点
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储组和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以是不包含重复元素的集合
HashSet集合练习
- 存储字符串并遍历
public class HashSetDemo {
public static void main(String[] args) {
//创建集合对象
HashSet<String> hs = new HashSet<String>();
//添加元素
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("world");
//遍历
for (String s : hs) {
System.out.println(s);
}
}
}
运行结果:
HashSet集合保证元素唯一性源码分析
//创建集合对象
HashSet hs = new HashSet();
//添加元素
hs.add(“hello”);
hs.add(“world”);
hs.add(“java”);
hs.add(“world”);
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//hash值和元素的hashCode()方法相关
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果哈希表未初始化,就对其进行初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 根据对象的哈希值计算对象的存储位置,如果该位置没有元素,就存储元素
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
/*
存入的元素和以前的元素 比较哈希值
如果哈希值不同,会继续向下执行,把元素添加到集合
如果哈希值相同,会调用对象的equals()方法比较
如果返回false,会继续向下执行,把元素添加到集合
如果返回true,说明元素重复,不存储
*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 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;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
HashSet集合存储元素:
- 要保证元素唯一性,需要重写hashCode()和equals()
常见数据结构之哈希表
哈希表
- JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组
- JDK8之后,在长度比较长的时候,底层实现了优化
案例:HashSet集合存储学生对象并遍历
需求:创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象
import java.util.Objects;
//定义学生类
public class Student {
private String name;
private int 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;
}
public Student() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
//快捷键重写hashCode()和equals()
@Override
public int hashCode() {
return Objects.hash(age, name);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
return age == other.age && Objects.equals(name, other.name);
}
}
import java.util.HashSet;
/*
案例:HashSet集合存储学生对象并遍历
需求:创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象
*/
public class HashSetDemo2 {
public static void main(String[] args) {
//创建HashSet集合对象
HashSet<Student> hs = new HashSet<Student>();
// 创建学生对象
Student s1 = new Student("小白", 12);
Student s2 = new Student("小黑", 13);
Student s3 = new Student("小红", 11);
Student s4 = new Student("小红", 11);
// 把学生添加到集合
hs.add(s1);
hs.add(s2);
hs.add(s3);
hs.add(s4);
//遍历集合
for (Student s : hs) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
运行结果:
LinkedHashSet集合概述和特点
LinkedHashSet集合特点:
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 有链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 有哈希表保证元素唯一性,也就是说没有重复的元素
LinkedHashSet集合练习
- 存储字符串并遍历
import java.util.LinkedHashSet;
/*
LinkedHashSet集合特点:
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 有链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 有哈希表保证元素唯一性,也就是说没有重复的元素
LinkedHashSet集合练习
- 存储字符串并遍历
*/
public class LinkedHashSetDemo {
public static void main(String[] args) {
//创建集合对象
LinkedHashSet<String> lhs = new LinkedHashSet<String>();
//添加元素
lhs.add("hello");
lhs.add("world");
lhs.add("java");
lhs.add("world");
//遍历
for (String s : lhs) {
System.out.println(s);
}
}
}
运行结果:
TreeSet集合概述和特点
TreeSet集合特点:
- 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
TreeSet():根据其元素的自然排序进行排序
TreeSet(Comparator comparator):根据指定的比较器进行排序 - 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以不包含重复元素的集合
TreeSet集合练习
- 存储整数并遍历
import java.util.TreeSet;
/*
TreeSet集合特点:
- 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
TreeSet():根据其元素的自然排序进行排序
TreeSet(Comparator comparator):根据指定的比较器进行排序
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以不包含重复元素的集合
TreeSet集合练习
- 存储整数并遍历
*/
public class TreeSetDemo {
public static void main(String[] args) {
//创建集合对象
TreeSet<Integer> ts = new TreeSet<Integer>();//由于<>里要用引用类型,所以要用int的包装类Integer
//添加元素
ts.add(55);
ts.add(22);
ts.add(44);
ts.add(33);
ts.add(11);
ts.add(11);//不包含重复元素
//遍历集合
for (int i : ts) {
System.out.println(i);
}
/*运行结果:按照自然顺序从小到大排序
11
22
33
44
55
*/
}
}
运行结果:
自然排序Comparable的使用
- 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
//定义学生类
public class Student implements Comparable<Student> {
private String name;
private int 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;
}
public Student() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student s) {
// TODO Auto-generated method stub
// return 0;//相同,不存储
// return 1;//存储的正序
// return -1;//存储的逆序
//按照年龄从小到大排序
int num = this.age - s.age;//这里this表示s2,s表示s1
// int num=s.age-this.age;//降序排列
//年龄相同时,按照姓名的字母顺序排序
int num2 = num == 0 ? this.name.compareTo(s.name) : num;//因为自然排序本身就可以用string类型,所以直接用s.name
return num2;//可以保证元素的唯一性
}
}
import java.util.TreeSet;
/*
自然排序Comparable的使用
- 存储学生对象并遍历,创建TreeSet集合使用**无参构造方法**
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
*/
public class TreeSetDemo2 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>();
//创建学生对象
Student s1 = new Student("xiaobai", 12);
Student s2 = new Student("dabai", 13);
Student s3 = new Student("xiaohei", 14);
Student s4 = new Student("dahei", 10);
Student s5 = new Student("xiaohong", 10);
Student s6 = new Student("xiaohong", 10);//元素唯一
//学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getAge());
}
/*运行结果:
ClassCastException
因为:学生类没有实现Comparable接口
*/
/*运行结果:
xiaobai,12
因为:此时compareTo()里返回的是0
*/
/*将compareTo()改为return 1
运行结果:
xiaobai,12
dabai,13
xiaohei,14
dahei,10
按照存储的顺序输出
*/
/*将compareTo()改为return -1
运行结果:
dahei,10
xiaohei,14
dabai,13
xiaobai,12
按照存储的顺序逆序输出
*/
}
}
运行结果:
结论:
- 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
- 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
比较器排序Comparator的使用
- 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
//定义学生类
public class Student {
private String name;
private int 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;
}
public Student() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
}
/*
*比较器排序Comparator的使用**
- 存储学生对象并遍历,创建TreeSet集合使用**带参构造方法**
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
*/
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetDemo3 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {//使用匿名内部类
@Override
public int compare(Student s1, Student s2) {
// TODO Auto-generated method stub
//this.age--s.age
//s1--s2
int num = s1.getAge() - s2.getAge();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num2;
}
});
//创建学生对象
Student s1 = new Student("xiaobai", 12);
Student s2 = new Student("dabai", 13);
Student s3 = new Student("xiaohei", 14);
Student s4 = new Student("dahei", 10);
Student s5 = new Student("xiaohong", 10);
Student s6 = new Student("xiaohong", 10);//元素唯一
//学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
运行结果:
结论:
- 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
案例:成绩排序
需求:用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成绩),并遍历该集合
要求:按照总分从高到低出现
//定义学生类
public class Student {
private String name;
private int chinese;
private int math;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChinese() {
return chinese;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public Student(String name, int chinese, int math) {
super();
this.name = name;
this.chinese = chinese;
this.math = math;
}
public Student() {
super();
}
@Override
public String toString() {
return "name=" + name + ", chinese=" + chinese + ", math=" + math + ", sum=" + getSum();
}
public int getSum() {
return this.chinese + this.math;
}
}
import java.util.Comparator;
import java.util.TreeSet;
/*
案例:成绩排序
需求:用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成绩),并遍历该集合
要求:按照总分从高到低出现
*/
public class TreeSetDemo4 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// TODO Auto-generated method stub
//按照总分从高到低出现
//s---this
//s2---s1
// int num=(s2.getChinese()+s2.getMath())-(s1.getChinese()+s1.getMath());
//主要条件
int num = s2.getSum() - s1.getSum();
//次要条件
int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num;
int num3 = num2 == 0 ? s1.getName().compareTo(s2.getName()) : num2;
return num3;
}
});
//创建学生对象
Student s1 = new Student("小白", 55, 66);
Student s2 = new Student("小黑", 54, 76);
Student s3 = new Student("小红", 67, 44);
Student s4 = new Student("小黄", 23, 64);
Student s5 = new Student("小蓝", 95, 78);
Student s6 = new Student("小绿", 94, 79);//总分相同,单科成绩不同
Student s7 = new Student("小紫", 94, 79);//总分和单科成绩都相同
//学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
//遍历集合
for (Student s : ts) {
System.out.println(s.toString());
}
}
}
运行结果:
案例:不重复的随机数
需求:编写一个程序,获取10个1–20之间的随机数,要求随机数不能重复,并在控制台输出
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import hello.randomDemo;
/*
案例:不重复的随机数
需求:编写一个程序,获取10个1--20之间的随机数,要求随机数不能重复,并在控制台输出
*/
public class SetDemo2 {
public static void main(String[] args) {
//创建Set集合对象
// Set<Integer> set=new HashSet<Integer>();
Set<Integer> set = new TreeSet<Integer>();
//创建随机数对象
Random r = new Random();
//判断集合长度是否小于10
while (set.size() < 10) {//小于10,就产生随机数添加到集合中
int number = r.nextInt(20) + 1;
set.add(number);
}
//变量集合
for (Integer i : set) {
System.out.println(i);
}
}
}
运行结果: