前言
深入学习集合要结合底层实现源码
简介:
Set集合,基础自Collection。特征是插入无序,不可指定位置访问。
Set集合的实现类可说是基于Map集合去写的。通过内部封装Map集合来实现的比如HashSet内部封装了HashMap。
Set集合的数据库不能重复(== 或 eqauls)的元素
Set集合的常用实现类有 HashSet 、TreeSet 、LinkedHashSet
Set
Set 注重独一无二的性质,该体系集合用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。
对象的相等性本质是对象hashCode 值(java 是依据对象的内存地址计算出的此序号)判断的,如果想要让两个不同的对象视为相等的,就必须覆盖 Object 的hashCode 方法和 equals 方法
可以看到Set接口的方法(是collection的子类):
Set接口有3个实现类:HashSet(Hash表)、TreeSet(二叉树)、LinkedHashSet(HashSet+LinkedHashMap)
HashSet集合(封装HashMap)
- 底层数据结构是哈希表(是一个元素为链表的数组) + 红黑树
TreeSet集合(基于TreeMap实现)
- 底层数据结构是红黑树(是一个自平衡的二叉树)
- 保证元素的排序方式
LinkedHashSet集合(HashSet+LinkedHashMap)
- 底层数据结构由哈希表(是一个元素为链表的数组)和双向链表组成
HashSet(Hash 表)
HashSet的要点:
实现Set接口
不保证迭代顺序允许元素为null
底层实际上是一个HashMap实例
非同步
初始容量非常影响迭代性能
HashSet封装HashMap实现
HashMap是映射,是键值对key-value,而HashSet添加元素不是键值对,实现方法就是创建一个final型object当做value
看看添加元素add()方法:
确实是key-value键值对,把元素e放在key位置,final型object放在value位置
元素不可重复
package com.company;
import java.util.HashSet;
import java.util.Set;
public class CollectionSet {
public static void main(String[] args){
Set<String> set=new HashSet<>();
set.add("hello");
set.add("hello");
System.out.println(set.size());
}
}
HashSet的元素个数为1
判断重复的底层是通过 == 或者 equals(可以查看源码)
hashSet的实现原理:
往Haset添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值 , 然后通过元素 的哈希值经过移位等运算,就可以算出该元素在哈希表中 的存储位置。
情况1: 如果算出元素存储的位置目前没有任何元素存储,那么该元素可以直接存储到该位置上。
情况2:
如果算出该元素的存储位置目前已经存在有其他的元素了,那么会调用该元素的equals方法与该位置的元素再比较一次,如果equals返回的是true,那么该元素与这个位置上的元素就视为重复元素,不允许添加,如果equals方法返回的是false,那么该元素运行添加
缺陷在插入的是对象就没法去重
package com.company;
import com.company.entity.User;
import java.util.HashSet;
import java.util.Set;
public class CollectionSet {
public static void main(String[] args){
Set<User> set=new HashSet<>();
User user1=new User("1","zhangsan");
User user2=new User("1","zhangsan");
System.out.println(user1==user2);
System.out.println(user1.equals(user2));
set.add(new User("1","zhangsan"));
set.add(new User("1","zhangsan"));
System.out.println(set.size());
}
}
== 和 equals比较地址值,而new的两个对象不是同一个对象
可以通过重写equals和hashCode方法解决:
在我们编写的类User中hashCode和equals方法:
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
重写:
@Override
public int hashCode() {
return this.id;
}
@Override
public boolean equals(Object object) {
if (object!=null && object instanceof User){
User user=(User)object;
if (this.id == user.id && this.name == user.name)
return true;
else
return false;
}
return false;
}
再运行上面的方法:
完成去重
操作HashSet就是在操作HashMap
TreeSet
功能基本和TreeMap相似
TreeSet的要点:
实现NavigableSet接口
可以实现排序功能
底层实际上是一个TreeMap实例
非同步
基于TreeMap实现,特点:插入无序内部有序(内部是链表,用迭代器、foreach遍历)
Set<Integer> set=new TreeSet<>();
set.add(1);
set.add(3);
set.add(2);
Iterator iterator=set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
输出(int型按升序排序):
如果想插入自定义对象User:
Set<User> userTreeSet=new TreeSet<>();
userTreeSet.add(new User(1,"zhangsan"));
userTreeSet.add(new User(3,"lisi"));
userTreeSet.add(new User(2,"wangwu"));
Iterator iteratorUser=userTreeSet.iterator();
while (iteratorUser.hasNext()){
System.out.println(iteratorUser.next());
}
报错,User无法转换成Comparable
TreeMap中的compare方法:
需要在User类中重写CompareTo方法(implements Comparable):
User.java:
package com.company.entity;
public class User implements Comparable<User> {
private int id;
private String name;
public User() {
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public int hashCode() {
return this.id;
}
@Override
public boolean equals(Object object) {
if (object!=null && object instanceof User){
User user=(User)object;
if (this.id == user.id && this.name == user.name)
return true;
else
return false;
}
return false;
}
//根据id排序
@Override
public int compareTo(User user) {
return this.id-user.id;
}
}
完成根据id排序
TreeSet与TreeMap相似,可实现排序功能
LinkedHashSet(HashSet+LinkedHashMap)
LinkedHashSet的要点:
迭代是有序的
允许为null底层实际上是一个HashMap+双向链表实例(其实就是LinkedHashMap)
非同步
性能比HashSet差一丢丢,因为要维护一个双向链表初始容量与迭代无关,LinkedHashSet迭代的是双向链表
跟HashSet、TreeSet一样,LinkedHashSet和LinkedHashMap相似(HashSet+双链表)
LinkedHashSet继承自HashSet,它的添加、删除、查询等方法都是直接super的HashSet的,唯一的不同就是它使用LinkedHashMap存储元素
和LinkedHashMap相似,那么LinkedHashMap的插入排序和访问排序LinkedHashSet有吗?
可以看构造方法:
没有对accessOrder(排序方式)的改变,即只能是默认值false,只有插入排序
LinkedHashSet<Integer> set=new LinkedHashSet<>();
set.add(1);
set.add(4);
set.add(2);
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
LinkedHashSet基于HashSet+双链表,与LinkedHashMap相似,增删改数据快,查数据慢
总结
Set集合都是基于Map实现,大部分的性质都和Map一样,仅仅用处不同:存储序列数据,不用键值对