1.1 Set集合概述和特点
- Set集合的特点
- 元素存取无序
- 没有索引、只能通过迭代器或增强for循环遍历
- 不能存储重复元素
1.2 哈希值【理解】
-
哈希值简介
是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
-
如何获取哈希值
Object类中的public int hashCode():返回对象的哈希码值 -
哈希值的特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
1.3 HashSet集合概述和特点【应用】
- HashSet集合的特点
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以是不包含重复元素的集合
1.4 HashSet集合保证元素唯一性源码分析【理解】
源码分析:
//创建集合对象
HashSet<String> hs = new HashSet<String>();
//添加元素
hs.add("hello");
hs.add("world");
hs.add("java");
----------------------------------------------
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;
}
1.5 常见数据结构之哈希表【理解】
案例:HashSet集合存储学生对象并遍历【应用】
- Student类
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 == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
- 测试
import java.util.HashSet;
public class test {
public static void main(String[] args) {
//创建HashSet集合对象
HashSet<Student> hs = new HashSet<Student>();
//创建学生对象
Student s4 = new Student("A", 33);
Student s5 = new Student("B",21);
Student s6 = new Student("B",21);
//把学生添加到集合
hs.add(s4);
hs.add(s5);
hs.add(s6);
//遍历集合(增强for)
for (Student s : hs) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
快速重写equals()和hashCode():
Alt+Insert找到equals()&hashCode(),选择IntelliJ Default
1.6 LinkedHashSet集合概述和特点【应用】
- LinkedHashSet集合特点
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 由哈希表保证元素唯一,也就是说没有重复的元素
import java.util.LinkedHashSet;
/*
LinkedHashSet集合特点
1:哈希表和链表实现的Set接口,具有可预测的迭代次序
2:由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
3:由哈希表保证元素唯一,也就是说没有重复的元素
*/
public class LinkedHashSetDemo {
public static void main(String[] args) {
//创建集合对象
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<String>();
//添加元素
linkedHashSet.add("hello");
linkedHashSet.add("world");
linkedHashSet.add("java");
linkedHashSet.add("world");
//遍历集合
for(String s : linkedHashSet) {
System.out.println(s);
}
}
}
2. Set集合排序
2.1 TreeSet集合概述和特点【应用】
-
TreeSet集合概述
-
元素有序,可以按照一定的规则进行排序,具体排序方式取决于构造方法
- TreeSet():根据其元素的自然排序进行排序
- TreeSet(Comparator comparator) :根据指定的比较器进行排序
-
没有带索引的方法,所以不能使用普通for循环遍历
-
由于是Set集合,所以不包含重复元素的集合
-
import java.util.TreeSet;
/*
TreeSet集合特点
1:元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
TreeSet():根据其元素的自然排序进行排序
TreeSet(Comparator comparator) :根据指定的比较器进行排序
2:没有带索引的方法,所以不能使用普通for循环遍历
3:由于是Set集合,所以不包含重复元素的集合
*/
public class TreeSetDemo01 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Integer> ts = new TreeSet<Integer>();
//添加元素
ts.add(10);
ts.add(40);
ts.add(30);
ts.add(50);
ts.add(20);
ts.add(30);
//遍历集合
for(Integer i : ts) {
System.out.println(i);
}
}
}
2.2 自然排序Comparable的使用【应用】
-
案例需求
- 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
-
实现步骤
- 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
- 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
学生类:
public class Student implements Comparable<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 int compareTo(Student s) {
//返回0认为重复不添加,返回1按存储顺序升序,返回-1按存储顺序降序
// return 0;
// return 1;
//按照年龄升序排列
/** 执行第一个add时,this.age和s.age都是指向29(s1)
执行第二个add是,this.age指向28(s2),s.age指向29(s1)*/
// int a=this.age;
// int b =s.age;
int num = this.age-s.age;
// int num s.age-this.age;//降序
//按照年龄,姓名排序
int num2 = num == 0 ? this.name.compareTo(s.name) : num;
return num2;
}
}
测试类:
import java.util.TreeSet;
public class Demo {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>();
//创建学生对象
Student s1 = new Student("A", 29);
Student s2 = new Student("B", 28);
Student s3 = new Student("C", 30);
Student s4 = new Student("D", 33);
Student s5 = new Student("A",29);
Student s6 = new Student("E",33);
//把学生添加到集合
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());
}
}
}
1.3 比较器排序Comparator的使用【应用】
-
案例需求
- 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
-
实现步骤
- 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
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;
}
}
测试:
import java.util.Comparator;import java.util.TreeSet;public class Demo { public static void main(String[] args) { //创建集合对象 TreeSet<Student> ts =new TreeSet<Student>(new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { //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("A", 29); Student s2 = new Student("B", 28); Student s3 = new Student("C", 30); Student s4 = new Student("D", 33); Student s5 = new Student("A",29); Student s6 = new Student("E",33); //把学生添加到集合 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集合存储多个学生信息(姓名,语文成绩,数学成绩),并遍历该集合
- 要求:按照总分从高到低出现
学生类:
public class Student { private String name; private int cg; private int mg; public Student() { } public Student(String name, int cg, int mg) { this.name = name; this.cg = cg; this.mg = mg; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getCg() { return cg; } public void setCg(int cg) { this.cg = cg; } public int getMg() { return mg; } public void setMg(int mg) { this.mg = mg; } public int getSum(){ return (this.cg+this.mg); }}
测试:
/*
需求:
用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成绩),并遍历该集合
要求:按照总分从高到低出现
思路:
1:定义学生类
2:创建TreeSet集合对象,通过比较器排序进行排序
3:创建学生对象
4:把学生对象添加到集合
5:遍历集合
*/
public class test {
public static void main(String[] args) {
TreeSet<Student> st = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// return 0;
//主要条件
int num = s2.getSum() - s1.getSum();
//次要条件
int num2 = num == 0 ? s1.getCg() - s2.getCg() : num;
int num3 = num2 == 0 ? s1.getName().compareTo(s2.getName()) : num2;
return num3;
}
});
//创建学生对象
Student s1 = new Student("q", 98, 100);
Student s2 = new Student("w", 95, 95);
Student s3 = new Student("e", 100, 93);
Student s4 = new Student("r", 100, 97);
Student s5 = new Student("t", 98, 98);
Student s6 = new Student("p", 97, 99);
Student s7 = new Student("p", 97, 99);
//把学生对象添加到集合
st.add(s1);
st.add(s2);
st.add(s3);
st.add(s4);
st.add(s5);
st.add(s6);
st.add(s7);
//遍历集合
for (Student s : st) {
System.out.println(s.getName() + "," + s.getCg() + "," + s.getMg() + "," + s.getSum());
}
}
}
案例:不重复的随机数案例
- 案例需求
- 编写一个程序,获取10个1-20之间的随机数,要求随机数不能重复,并在控制台输出
*
需求:
编写一个程序,获取10个1-20之间的随机数,要求随机数不能重复,并在控制台输出
思路:
1:创建Set集合对象
2:创建随机数对象
3:判断集合的长度是不是小于10
是:产生一个随机数,添加到集合
回到3继续
4:遍历集合
*/
public class SetDemo {
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) {
//产生一个随机数,添加到集合
int number = r.nextInt(20) + 1;
set.add(number);
}
//遍历集合
for(Integer i : set) {
System.out.println(i);
}
}
}