Set系列集合
无序:存取顺序不一致
不重复:可以去除重复
五索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素
Set集合的实现类
HashSet:无序、不重复、无索引
LinkedHashSet:有序、不重复、无索引
TreeSet:可排序、不重复、无索引
Set接口中的方法基本上与Collection的API一致。
public static void main(String[] args) {
//创建一个Set集合的对象
Set<String> s = new HashSet<>();
//2.添加元素
//如果当前元素是第一次添加,那么可以添加成功,返回true
//如果当前元素第二次添加,那么添加失败,返回false
boolean r1 = s.add("张三");
boolean r2 = s.add("李四");
boolean r3 = s.add("王五");
//打印集合
//迭代器遍历
Iterator<String> it = s.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
//增强for遍历
for (String s1 : s) {
System.out.println(s1);
}
//Lambda表达式遍历
s.forEach(str-> System.out.println(str));
}
HashSet底层原理
HashSet集合底层采取哈希表储存数据
哈希表是一种对于增删改查数据性能都比较好的结构
哈希表组成
JDK8之前:数组+链表
JDK8之后:数组+链表+红黑树
哈希值
对象的整数表现形式
- 根据hashCode方法算出来的int类型的整数
- 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算。
- 一般情况下,会重写hashCode方法 ,利用对象内部属性值计算哈希值 。
对象的哈希值特点
如果没有重写hashCode方法,不同对象计算出的哈希值是不同的。
如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的。
在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
public static void main(String[] args) {
//1.创建对象
Student s1 = new Student("张三", 23);
Student s2 = new Student("张三", 23);
//2.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
//如果已经重写hashCode方法,不同对象只要属性值相同,计算出的和哈希值就是一样的
System.out.println(s1.hashCode());//24022543
System.out.println(s2.hashCode());//24022543
//再在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)
System.out.println("abc".hashCode());//96354
System.out.println("acD".hashCode());//96354
}
HashSetJDK8以前底层原理
- 创建一个默认长度16,默认加载因子0.75的数组,数组名table
HashSet hm = new HashSet<>(); - 根据元素的哈希值跟数组的长度计算出应存入的位置
int index = (数组长度 - 1)&哈希值; - 判断当前位置是否为null,如果是null直接存入
- 如果位置不为null,表示有元素,则调用equals方法比较属性值
- 一样:不存 不一样:存入数组,形成链表
JDK8以前:新元素存入数组,老元素存在新元素下面
JDK8以后:新元素直接挂在老元素下面
JDK8以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树。
如果集合中储存的时自定义对象,必须要重写hashCode和equals方法.
HashSet三个问题
hashset为什么存和取的顺序不一样
存的位置是计算出来的,包含链表,存和取的顺序不一样
hashset为什么没有索引
因为hashset包含数组,链表,红黑树三个组合而成.
hashset是利用什么机制去重的呢
通过hsahcode方法确定位置,通过equals方法判断是否一样.
LinkedHashSet底层原理
有序,不重复,无所引.
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构是依然哈希表,只是每个元素有额外的多了一个双链表的机制记录储存的顺序.
public static void main(String[] args) {
//创建三个学生对象
Student s1 = new Student("zhangsan", 23);
Student s2 = new Student("lisi", 22);
Student s3 = new Student("wangwu", 33);
Student s4 = new Student("wangwu", 33);
//创建集合用来添加学生
LinkedHashSet<Student> hs = new LinkedHashSet<>();
//添加元素
System.out.println(hs.add(s1));//true
System.out.println(hs.add(s2));//true
System.out.println(hs.add(s3));//true
System.out.println(hs.add(s4));//false
//打印集合
System.out.println(hs);//[Student{name = zhangsan, age = 23}, Student{name = lisi, age = 22}, Student{name = wangwu, age = 33}]
}
- LinkedHashSet集合的特点和原理是怎样的?
有序,不重复,无所引
底层基于哈希表,使用双链表记录添加顺序 - 在以后如果数据要去重,我们使用哪个?
默认使用HashSet
如果要求去重且存取有序,才使用LinkedHashSet.
TreeSet
特点
不重复,无所引,可排序
可排序:按照元素的默认规则(由大到小)排序.
TreeSet集合底层是基于红黑树的数据结构实现排序的,怎删改查性能都较好.
public static void main(String[] args) {
//创建treeset集合对象
TreeSet<Integer> ts = new TreeSet<>();
//添加元素
ts.add(1);
ts.add(6);
ts.add(5);
ts.add(2);
ts.add(3);
System.out.println(ts);//[1, 2, 3, 5, 6]
}
TreeSet集合默认的规则
对于数值类型:integer,Double,默认按照从小到大的顺序进行排序.
对于字符,字符串类型:按照字符再Ascii码表中的数字升序进行排序,
TreeSet的两种比较方式
方式一:默认排序/自然排序:javabean类实现Comparable接口指定比较规则
this:表示当前要添加的元素
O:表示已经在红黑树存在的元素
返回值:
负数:认为要添加的元素是小的,存左边.
正数:认为要添加的元素是大的,村右边.
0:认为要添加的元素已经存在,舍弃
public class Student implements Comparable<Student>{
//this:表示当前要添加的元素
//O:表示已经在红黑树存在的元素
//返回值:
//负数:认为要添加的元素是小的,存左边.
//正数:认为要添加的元素是大的,村右边.
//0:认为要添加的元素已经存在,舍弃
@Override
public int compareTo(Student o) {
//根据年龄排序,年龄相同视为同一同学
int i = this.getAge() - o.getAge();
return i;
}
public static void main(String[] args) {
//创建三个学生对象
Student s1 = new Student("zhangsan", 32);
Student s2 = new Student("lisi", 23);
Student s3 = new Student("wangwyu", 23);
//创建treeset集合对象
TreeSet<Student> ts = new TreeSet<>();
//添加学生对象
ts.add(s1);
ts.add(s2);
ts.add(s3);
System.out.println(ts);//按照年龄大小排序
}
方式二:比较器排序
创建TreeSet对象的时候,传递比较器Comparator指定规则
public static void main(String[] args) {
//创建集合
/*TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//按照长度排序
int i = o1.length() - o2.length();
//如果长度一样则按照首字母排序
i = i == 0 ? o1.compareTo(o2) : i;
return i;
}
});*/
TreeSet<String> ts = new TreeSet<>(o1,o2) {
//按照长度排序
int i = o1.length() - o2.length();
//如果长度一样则按照首字母排序
i = i == 0 ? o1.compareTo(o2) : i;
return i;
}
});
//添加元素
ts.add("c");
ts.add("ab");
ts.add("df");
ts.add("qwer");
System.out.println(ts);//[c, ab, df, qwer]
}
默认用第一种,如果第一种不能满足当前需求,就使用第二种.
方式一方式二同时存在的时候,使用的是方式二.
总结
- 如果想要集合中的元素可重复
用ArrayList集合,基于数组的.(用的最多 ) - 如果想对集合中的元素去重
用HashSet集合,基于哈希表的.(用的最多) - 如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
用LinkedList集合,基于链表的 - 如果想对集合中的元素去重,而且保证存取顺序
用LinkedHashSet集合,基于哈希表和双链表 ,效率低于HashSet. - 如果想要对集合中的元素进行排序
用TreeSet集合,基于红黑数.后续也可以用List集合实现排序