Collection
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系列集合
- 无序:存储顺序不一致
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素
- Set集合的方法基本上与Collection的API一致
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class A01_SetDemo1 {
public static void main(String[] args) {
/*
利用Set系列的集合,添加字符串,并使用多种方式遍历
迭代器
增强for
lambda表达式
*/
// 1. 创建一个Set集合的对象
Set<String> s = new HashSet<>();
// 2. 添加元素
// 如果当前元素是第一次添加,则可以添加成功,返回true;否则,添加失败,返回false
boolean r1 = s.add("张三");
boolean r2 = s.add("张三");
System.out.println(r1); // true
System.out.println(r2); // false
s.add("李四");
s.add("王五");
// 3. 打印集合
// 无序
System.out.println(s); // [李四, 张三, 王五]
// 迭代器遍历
Iterator<String> it = s.iterator();
while(it.hasNext()) {
String str = it.next();
System.out.println(str);
}
// 增强for
for (String str : s) {
System.out.println(str);
}
// lambda表达式
s.forEach(str-> System.out.println(str));
}
}
Set集合的实现类
- HashSet:无序、不重复、无索引
- LinkedHashSet:有序、不重复、无索引
- TreeSet:可排序、不重复、无索引
HashSet
HashSet:无序、不重复、无索引
HashSet底层原理:
- HashSet集合底层采取哈希表存储数据
- 哈希表是一种对于增删改查数据性能都较好的结构
哈希表的组成:
- JDK8之前:数组 + 链表
- JDK8开始:数组 + 链表 + 红黑树
①创建一个默认长度16,默认加载因子为0.75的数组,数组名为table。
当数组存了16 * 0.75 = 12个元素的时候,数组会扩容为原来的两倍。
当链表长度大于8而且数组长度大于等于64时,链表会自动转换为红黑树,以提高查找效率。 - ②根据元素的哈希值跟数组的长度计算出应存入的位置:
int index = (数组长度 - 1) & 哈希值
③判断当前位置是否为null,表示有元素,则调用equals方法比较属性值
④如果位置不为null,表示有元素,则调用equals方法比较属性值
⑤属性值一样:不存;属性值不一样:存入数组,形成链表
JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8以后:新元素直接挂在老元素下面
哈希值:
- 根据hashCode方法计算出来的int类型的整数
- 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
如果集合中存储的是自定义对象,必须重写hashCode和equals方法 - 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
- 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
- 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
- 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)。
// 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;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param 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;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
// 测试类
public class A02_HashSetDemo1 {
public static void main(String[] args) {
/*
哈希值:
对象的整数表现形式
1. 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
2. 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
3. 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)。
*/
// 1. 创建对象
Student s1 = new Student("zhangsan", 23);
Student s2 = new Student("zhangsan", 23);
// 2. 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
// 如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
System.out.println(s1.hashCode()); // -1461067292
System.out.println(s2.hashCode()); // -1461067292
// 在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)。
System.out.println("abc".hashCode()); // 96354
System.out.println("acD".hashCode()); // 96354
}
}
LinkedHashSet
LinkedHashSet:有序、不重复、无索引
LinkedHashSet底层原理:
底层数据结构依然是哈希表,只是每个元素额外的多了一个双向链表的机制记录存储的顺序。
import java.util.LinkedHashSet;
public class A04_LinkedHashSetDemo {
public static void main(String[] args) {
// 1. 创建4个学生对象
Student s1 = new Student("zhangsan", 23);
Student s2 = new Student("lisi", 24);
Student s3 = new Student("wangwu", 25);
Student s4 = new Student("zhangsan", 23);
// 2. 创建集合的对象
LinkedHashSet<Student> lhs = new LinkedHashSet<>();
// 3. 添加元素
System.out.println(lhs.add(s1)); // true
System.out.println(lhs.add(s2)); // true
System.out.println(lhs.add(s3)); // true
System.out.println(lhs.add(s4)); // false
// 4. 打印集合
System.out.println(lhs);
// [Student{name = zhangsan, age = 23}, Student{name = lisi, age = 24}, Student{name = wangwu, age = 25}]
}
}
TreeSet
特点:
- 可排序、不重复、无索引
- 可排序:按照元素的默认规则(从小到大)排序
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好
import java.util.Iterator;
import java.util.TreeSet;
public class A04_TreeSetDemo1 {
public static void main(String[] args) {
/*
利用TreeSet存储整数并进行排序
*/
// 1. 创建TreeSet集合对象
TreeSet<Integer> ts = new TreeSet<>();
// 添加元素
ts.add(4);
ts.add(5);
ts.add(1);
ts.add(3);
ts.add(2);
// 3. 打印集合
// System.out.println(ts); // [1, 2, 3, 4, 5]
// 4. 遍历集合(三种遍历)
// 迭代器
/* Iterator<Integer> it = ts.iterator();
while (it.hasNext()) {
int i = it.next();
System.out.println(i);
}*/
// 增强for
/* for (Integer t : ts) {
System.out.println(t);
}*/
// lambda表达式
ts.forEach(i-> System.out.println(i));
}
}
TreeSet集合排序默认的规则:Javabean类实现Comparable接口指定比较规则
- 对于数值类型:Integer、Double,默认按照从小到大的顺序进行排序。
- 对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序。
// Student类
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;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
@Override
// this:表示当前要添加的元素
// o:表示已经在红黑树存在的元素
// 返回值:负数:表示当前要添加的元素是小的,存左边
// 正数:表示当前要添加的元素是大的,存右边
// 0:表示当前要添加的元素已经存在,舍弃
public int compareTo(Student o) {
// 指定排序的规则
// 要求按照学生的年龄进行升序排序,同年龄按照名字字母排列(暂不考虑中文)
int result = this.getAge() - o.getAge();
return result;
}
}
// 测试类
import java.util.TreeSet;
public class A04_TreeSetDemo2 {
public static void main(String[] args) {
/*
需求:创建TreeSet集合,并添加3个学生对象
学生对象属性:姓名、年龄
要求按照学生的年龄进行排序,同年龄按照名字字母排列(暂不考虑中文)
同姓名、同年龄认为是同一个人
方式一:默认的排序规则/自然排序
Student类实现Comparable接口,重写里面的抽象方法,再指定比较规则
*/
// 创建3个学生对象
Student s1 = new Student("zhangsan", 23);
Student s2 = new Student("lisi", 24);
Student s3 = new Student("wangwu", 25);
// 创建集合对象
TreeSet<Student> ts = new TreeSet<>();
// 添加元素
ts.add(s3);
ts.add(s2);
ts.add(s1);
// 打印集合
System.out.println(ts);
}
}
TreeSet集合比较器排序:创建TreeSet对象的时候,传递比较器Comparator指定规则
package com.itheima.a05myset;
import java.util.TreeSet;
public class A06_TreeSetDemo3 {
public static void main(String[] args) {
/*
需求:请自行选择比较器排序和自然排序两种方式
要求:存入四个字符串,”c", "ab", "df", "qwer"
按照长度排序,如果一样则按照首字母排序
采取第二中排序方式:比较器排序
*/
// 1. 创建集合
// o1:表示当前要添加的元素
// o2: 表示已经在红黑树存在的元素
TreeSet<String> ts = new TreeSet<>((o1, o2)->{
// 按照长度进行排序
int i = o1.length() - o2.length();
// 如果长度一样,按照首字母排序
i = i == 0 ? o1.compareTo(o2) : i;
return i;
});
// 2. 添加元素
ts.add("c");
ts.add("ab");
ts.add("df");
ts.add("qwer");
// 3. 打印集合
System.out.println(ts); // [c, ab, df, qwer]
}
}
使用选择:
1. 如果想要集合中的元素可重复
- 用ArrayList集合,基于数组的。(用的最多)
2. 如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
- 用LIinkedList集合,基于链表的。
3. 如果想对集合中的元素去重
- 用HashSet集合,基于哈希表的。(用到最多)
4. 如果想对集合中的元素去重,而且保证存取顺序
- 用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。
5. 如果想对集合中的元素进行排序
- 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。