Set集合
set集合类似于一个罐子,可以依次把多个对象放进去,罐子并不能记住顺序,实际上Set集合与Collection集合基本相同,没有额外的方法,只是不允许有重复元素。
HashSet类
HashSet的特点:(1)不能保证元素的排列顺序,顺序可能变化
(2)不同步,多线程同时访问需要手动代码实现
(3)集合元素值可以使NULL
HashSet存储的问题:存储元素的槽位,通常称之为‘桶’,当多个元素的Hashcode值相同是,我们称之为哈希碰撞,有可能在桶中有多个元素,他们的存储方式是链式的。
hash算法的优点在于速度
详细的解释一下:当从HashSet访问元素时,HashSet先算出该元素的hashcode值,然后直接从该hashcode值对应的位置中取出该元素。
hashcode的重写方法
第一步:根据实例变量计算出一个 int 类型的hashcode值
实例变量类型 计算方式
boolean hashCode=(f?0:1)
整数类型 hashCode=(int)f
long hashCode=(int)(f^(f>>>32))
float hashCode=Float.floatToIntBits(f);
引用类型 hashCode=f.hashCode()
第二步://计算出多个hashcode值组合并返回,
return f1.hashcode()+(int)f2; 同时为了尽可能地避免偶然相等的情况,我们做了改进
return f1.hashcode()*19+(int)f2*31; //引入两个权重,权重均为素数,且不宜过大引发溢出
我们来看看源码:
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
LinkedHashSet类
LinkedHashSet类是HashSet的一个子类,它同时拥有链表维护次序,当遍历LinkedHashSet类集合元素时,会按照添加的顺序访问集合元素。
举例:
LinkedHashSet<Object> obj = new LinkedHashSet<>();
obj.add("aaa");
obj.add("ccc");
System.out.println(obj);
obj.remove("aaa");
obj.add("aaa");
System.out.println(obj);
}
输出LinkedHashSet集合元素时,元素的顺序与输入一致
TreeSet类
TreeSet是SortedSet接口的实现类,可以确保元素处于排序状态。实现方法举例说明:
TreeSet<Integer> num = new TreeSet<>();
num.add(5);
num.add(10);
num.add(23);
num.add(-1);
System.out.println(num);
System.out.println(num.first());
System.out.println(num.last());
System.out.println(num.headSet(4)); //返回小于4的子集
System.out.println(num.tailSet(5)); //返回大于5的子集
System.out.println(num.subSet(-2, 11)); // 返回大于-2,小于11的子集
TreeSet采用红黑树的数据结构排序,支持两种排序结构,分别是自然排序和定制排序
自然排序
TreeSet自然排序会调用CompareTo(Object obj)方法来实现,此对象必然实现Comparable接口。举例如下:
class Z implements Comparable{
int age;
public Z(int age){
this.age=age;
}
public boolean equals(Object obj){
return true;
}
public int compareTo(Object obj){
return 1;
}
}
public class demo{
public static void main(String[] args) {
TreeSet set = new TreeSet();
Z z1=new Z(6);
set.add(z1);
System.out.println(set.add(z1));
System.out.println(set);
((Z) (set.first())).age=9;
System.out.println(((Z) (set.first())).age);
}
}
自然排序存储元素时,跟树根节点比较,分左右,比树根小放左边,比树根大放左边,与树根一致是不存储。
定制排序
自然排序会将集合中元素大小按升序排列,然而现实中我们并不一定需要升序排列,可能需要降序排列。通过Comparator接口的 int compare(T o1,T o2)方法,比较大小,通过返回值正负表明大小。正大负小。举例:
class M{
int age;
public M(int age) {
this.age = age;
}
@Override
public String toString() {
return "M [age:"+age+"]";
}
}
public class Treeset {
public static void main(String[] args) {
Treeset ts = new Treeset((o1,o2) ->{
M m1 = (M) o1;
M m2 = (M) o2;
});
ts.add(new M(5));
ts.add(new M(9));
ts.add(new M(-3));
System.out.println(ts);
}
}
定制排序实现降序排列。
注意:通过Comparator对象实现定制排序时,也不可添加不同的对象。定制排序时,不是由TreeSet排序,而是由Comparator对象负责排序当然也可以是Lambda表达式负责。
Lambda表达式
Java 8更新后,支持将代码块作为方法参数,允许使用更简洁的方法建立只有一个抽象方法的接口。
Lambda表达式是吸纳的是匿名方法,当代码块只有一行是,可以不带{},当表达式形参只有一个时,不用带();
Lambda表达式的目标类型必须有明确的函数式接口,只能为函数式接口创建对象,只能实现一个方法,只能为只有一个抽象方法的接口创建对象。
各Set实现类性能分析
总结:HashSet和TreeSet是Set的两个经典实现。在选择上,HashSet的性能总比TreeSet好!!特别是增、删、查上,只有当需要一个排序Set时,才会用TreeSet。
另:HashSet 还有一个子类,LinkedHashSet,因为其特殊的链表结构,所以增、删会慢一点,但遍历性能好一点。
当然,Set三种实现类(Hash、Tree、Enum)都是线程不安全的,多线程操作时,需要手动保证,一般是通过集合工具类的synchronizedSortedSet包装该Set集合。最好在运行开始就定义。
SortdeSet s=Collection.synchronizedSortdeSet(new Treeset());