java中包含四大集合分别为 set、list、queue、map其中set、list和queue都是继承Collection接口而map是单独的接口,java编程时会遇到大量的集合问题所以这次我就对这些集合梳理一下以加深印象。
set和map
set和map虽然不是继承的同一个接口但是这两个集合和他们对应的具体实现都有十分类似之处,如果把map的一组key-value看做一个元素的话那么set和map基本就是一样的,所以这里把这两个集合一起梳理更加合适
1.HashSet 和 HashMap、Hashtable
HashSet 特点:唯一性,无序性,线程不同步性。HashSet中的元素是唯一的,同时它不能保证元素的排列顺序,它元素的顺序是随机排放的,同时 它是不同步的如果有两个或两个以上的线程同时修改HashSet那么必须通过代码来保证同步,此外HashSet元素可以为null。
HashMap特点:唯一性,无序性,线程不同步性。HashMap的这两点和HashSet一致都是唯一的无序的但是这种唯一和无序是对于key而言map中的value可以重复出现,同时HashMap的key和value都可以为null。
Hashtable特点:唯一性,无序性,线程安全,低效。与HashMap相比Hashtable在多线程的程序中更加好用但是相应的他的效率要比HashMap低,此外Hashtable中的key和value不能为空
这三个集合在存入元素时都会调用hashCode()方法得到hashcode的值,然后根据hashcode来决定key在hashmap中的存储位置,如果两个元素通过equals方法比较返回true单他们hashCode方法的返回值不相等,该元素也可以添加但是集合会把他们的key存储到不同的位置,也就是说这三个集合对象是否相等是通过equals和hashcode两个方法结果都相等来判断的。下面通过一段代码来具体看这个问题:
package yes;
import java.util.HashMap;
import java.util.HashSet;
class A{
@Override
public boolean equals(Object o){
return true;
}
}
class B{
@Override
public int hashCode(){
return 1;}
}
class c{
@Override
public boolean equals(Object o){
return true;
}
@Override
public int hashCode(){
return 0;}
}
public class set {
public static void main(String arg[]){
HashSet set=new HashSet<>();
HashMap map=new HashMap<>();
set.add(new A());
set.add(new A());
set.add(new B());
set.add(new B());
set.add(new c());
set.add(new c());
System.out.print(set+"\n");
map.put(new A(), null);
map.put(new A(), null);
map.put(new B(), null);
map.put(new B(), null);
map.put(new c(), null);
map.put(new c(), null);
System.out.print(map);
}
}
这段程序写入了三个类,并分别重写了equals、hashCode方法中的一个或全部,运行之后程序的结果如下:
可以看出重写了两个方法的c类只有一个值被放到了集合中,而其他两个类的两个对象则全部都有,这就是因为对于每一个c类的对象他们的equals、hashCode方法返回结果都一样所以第二个加入的元素会替换掉第一个元素。所以这里应该注意如果想把一个类的对象放入到hashset或hashmap中时,应该重写equals、hashCode两个方法,如果equals返回结果为true那么两个对象的hashCode返回值应该相同。
对于hashset和hashmap还有一点需要注意的是,放入集合的元素尽量不要修改参与equals、hashCode方法计算返回值的值,如果修改了将会导致集合无法正确的操作这些集合元素,例如:
package yes;
import java.util.HashSet;
import java.util.Iterator;
public class set2 {
public static void main(String arg[]){
HashSet<settest> set=new HashSet<>();
set.add(new settest(1));
set.add(new settest(2));
set.add(new settest(3));
System.out.print(set+"\n");
Iterator it=set.iterator();//生成迭代器
settest one=(settest)it.next();//取出迭代器中的第一个对象并改变value值
one.value=4;//value值参与了equals、hashCode方法计算返回值,改变它将使集合无法正常操作
System.out.print(set+"\n");
set.remove(new settest(4));//set删除new settest(4) 修改后的这个对象但是无法删除
set.remove(new settest(2));//set删除new settest(2) 没有修改过的这个对象可以删除
System.out.print(set+"\n");
}
}
class settest{
int value;
public settest(int v){
this.value=v;
}
@Override
public String toString(){
return "this is "+value;
}
@Override
public boolean equals(Object o){//重写equals
if(this==o)
return true;
if(o!=null&&o.getClass()==settest.class){
settest set=(settest)o;
return this.value==set.value;
}
return false;
}
@Override
public int hashCode(){//重写hashCode
return this.value;
}
}
2.LinkedHashSet 和LinkedHashMap
LinkedHashSet 和LinkedHashMap也十分相似,它们都使用双向链表来维护key的次序并且该链表负责set和map的迭代顺序,它的顺序与key(value)的插入顺序一致。如下:
package yes;
import java.util.LinkedHashMap;
public class Linkedset {
public static void main(String arg[]){
LinkedHashMap map=new LinkedHashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
System.out.print(map);
}
}
由于LinkedHashSet 和LinkedHashMap需要维护元素的插入顺序所以与hashset和hashmap相比较性能略低,但是在迭代访问元素内容的时候LinkedHashSet 和LinkedHashMap具有更好的性能。
3.TreeSet和TreeMap
TreeSet和TreeMap分别实现了SortedSet和SortedMap接口,这两个集合的特点就是实现了有序排列,首先先看一下TreeSet和HashSet的不同之处:
package yes;
import java.util.TreeSet;
public class treeset {
public static void main(String arg[]){
TreeSet set=new TreeSet<>();
set.add(1);
set.add(10);
set.add(2);
set.add(0);
System.out.print(set+"\n");
System.out.print(set.first()+" "+set.last()+"\n");//返回第一个和最后一个元素
System.out.print(set.lower(2)+" "+set.higher(2)+"\n");//返回比2小的最大元素,返回比2大的最小元素
}
}
可见treeset中添加了许多hashset没有的方法,而且这些方法都跟排序有关系并且它的排列顺序不是根据元素值插入顺序决定的,而是根据元素实际大小来决定的,同理treemap中的元素也是根据key值的大小来进行排序的,在TreeSet和TreeMap中集合元素是根据红黑树的数据结构来进行存储的,它们支持两种排序方法,自然排序和定制排序。
自然排序是根据Comparable中的compareto方法来实现的,所以这就要求TreeSet和TreeMap中的元素类要实现Comparable接口如果没有实现则将显示编译错误,下面一段代码来查看compareto的实际作用:
package yes;
import java.util.TreeSet;
public class treeset2 {
public static void main(String arg[]){
TreeSet set=new TreeSet<>();
set.add(new settest2(5));
set.add(new settest2(1));
set.add(new settest2(7));
System.out.print(set);
}
}
class settest2 implements Comparable{
int value;
public settest2(int v){
this.value=v;
}
@Override
public int compareTo(Object o) {
settest2 m=(settest2)o;
return this.value>m.value?1:
this.value<m.value?-1:0;
}
@Override
public boolean equals(Object o){
if(this==o)
return true;
if(o!=null&&o.getClass()==settest2.class){
settest2 f=(settest2) o;
return this.value==f.value;
}
return false;
}
public String toString(){
return "this is "+this.value;
}
}
上面代码的输出结果是按照每个对象的value值大小来排序的,与hashset、hashmap同样的,treeset和treemap不要随意改变元素参与compareto返回值计算的变量否则会使集合操作元素时出现错误。
treeset和treemap还有一种方法来用于排序它就是定制排序,由于定制排序需要用到java8的lambda表达式所以这里就不具体举例了。
4.EnumSet 和 EnumMap
EnumSet 和 EnumMap是专门为枚举类设计的集合,他们中的元素必须是指定枚举类的枚举值,EnumSet 和 EnumMap也是有序的元素的顺序为枚举类定义的元素顺序。下面以EnumMap来看看他们的具体方法:
package yes;
import java.util.EnumMap;
public class enumset {
public static void main(String arg[]){
EnumMap map=new EnumMap<>(week.class);
map.put(week.sunday, "周日");
map.put(week.saturday, "周六");
map.put(week.monday, "周一");
map.put(week.tuesday, "周二");
System.out.print(map);
}
}
enum week{
sunday,monday,tuesday,wednesday,thursday,friday,saturday
}
set集合对比分析
在set集合中,enumset是性能最好的,但是只能保存一个枚举类的枚举值。一般情况下的set集合都简易使用hashset因为它的可以添加任意对象且性能良好,treeset适用于保持有序的set集合,对于需要经常遍历的集合使用linkedhashset比较好,另外hashset,treeset,enumset都是线程不安全的,可以通过collections来进行包装使集合在多线程编程中性能更好。
map集合对比分析
map集合的性能跟set大体类似,除此之外还有一个hashtable适用于多线程编程。
list
相对于set和map,list集合比较简单,它可以使用collection中的全部方法,与collection不同的是list是有序的他根据元素的插入顺序来排序并以插入位置为索引获取该元素,此外list判断两个元素是否相同是通过equals方法进行比较这点与hashmap,hashset,treemap,treeset不同,要牢记这几个集合判断元素是否相同的方法,下面通过一段代码来对list进行简单的分析:
package yes;
import java.util.ArrayList;
import java.util.List;
public class list {
public static void main(String arg[]){
List list=new ArrayList<>();
list.add(new listtest(1));
list.add(new listtest(2));
list.add(new listtest(3));
list.remove(new listtest(1));//删除equals方法相同的那个元素
List list2=new ArrayList<>();
list2.add(1);
list2.add(1);
list2.remove(1);
System.out.print(list+"\n"+list2);
}
}
class listtest{
int value;
public listtest(int v){
this.value=v;
}
@Override
public boolean equals(Object o){//重写equals
/*if(this==o)
return true;
if(o!=null&&o.getClass()==settest.class){
settest set=(settest)o;
return this.value==set.value;
}*/
return true;
}
}