您的位置:首页 > 其它

关于HashSet、LinkedHashSet和TreeSet以及Comparable和Comparator

2017-03-31 23:04 471 查看

一、HashSet和LinkedHashSet基础

1 . Set中的元素在底层存储的位置是无序的;

2 . Set中的元素是不可重复的;

String str1 = new String("abc");
String str2 = new String("abc");
//要求对象各属性值均不同。即使是new的对象,只要属性值相同,也视为重复的。如str1和str2
//因为String重写了Object的hashcode()方法,根据属性值计算hash值,因此属性值相同,hash值就相同。


3 . 因此,要求存入HashSet中的自定义对象要重写hashCode()和equal()方法,保证Set中元素的不可重复性;

当向HashSet中添加对象时,会首先调用对象的hashcode()方法,计算此对象的hash值,此hash值决定了此对象存在HashSet中的位置。若此位置之前没有对象,则该对象直接存储到此位置。若此位置已有对象,再通过equal()方法比较两个对象是否相同。若相同,后一个对象就不能再添加进去。(要求最好保证hashCode比较的结果要与equal比较的结果一致)

class Person{
int age;
String name;
//常用重写hashCode的方法(可在IDE自动生成)
public int hashCode(){
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
}


4 . 使用哈希算法,降低了比较的复杂性,若不使用哈希算法,每次向HashSet中存入元素,都要与HashSet中已有的所有元素进行比较,过于复杂。

5 . HashSet按hash算法来存储集合中的元素,因此具有很好的存取和查找性能。但是不能保证元素的排列顺序。

6 . LinkedHashSet是HashSet的子类,在根据hashCode值决定元素存储位置的同时,使用链表为元素建立前向和后向索引,来维护元素的词序,这使得元素是以插入顺序保存的。

7 . 因此,LinkedHashSet的插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。

二、关于HashSet的底层实现

HashSet的底层用的是HashMap,当用户创建HashSet实例时,会为该实例创建一个HashMap类型的成员变量。对HashSet实例的操作,
4000
其实都是在对该HashMap成员变量操作。如下源码所示:

private transient HashMap<E,Object> map;//HashMap成员变量
private static final Object PRESENT = new Object();//map的value存放一个静态的Object常量

//构造器中初始化map
public HashSet() {
map = new HashMap<>();
}

//对HashSet的操作内部封装的都是对map的操作
//map.put()返回的是key先前对应的value,返回null,则说明先前map中没存放该k-v对,set.add成功,返回true。
//否则,返回的value为PRESENT,则set.add失败,返回false,这说明map中先前存在该k-v对。
//(实际上map中依然成功添加了该key-value对,只不过与k-v值未发生变化)
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

//同理,HashSet的迭代器,调用的的是对应map的keySet的迭代器。HashSet中的值存放在对应map的key中。
public Iterator<E> iterator() {
return map.keySet().iterator();
}

//更多信息可查看HashSet的API


三、关于LinkedHashSet的底层实现

1 . LinkedHashSet作为HashSet的子类,并没有增加任何新的方法。只提供了四个构造方法,且内部都是调用了父类HashSet的带boolean标识符的构造方法。

//LinkedHashSet的构造方法调用父类的带标参的构造方法。
public LinkedHashSet() {
super(16, .75f, true);
}

//hashSet的带标识符参数的构造方法,该方法为对象实例化一个LinkedHashMap变量。
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}


2 . 因此,可知LinkedHashSet中的元素其实是存储在其内部的LinkedHashMap实例属性中。

四、关于TreeSet

1 . TreeSet中存储的元素会自动按大小顺序排列。String、包装类等默认按从小到大的顺序。(字符串升序)

2 . 存入TreeSet中元素会自动比较大小,因此,TreeSet中存放的元素必须是同一类型。且自定义类必须实现comparable接口或者使用TreeSet(Comparator<? super E> comparator)构造方法传入所存放类型的外部比较器,否则向TreeSet中添加该类的对象时,会报ClassCastException(类型转换异常)。

3 . 自定义类重写comparable接口的compareTo( )方法时,可以指定按照自定义类某个属性排序。

public class People implements Comparable<People>{
private String name;
private int age;

//若在重写的compareTo()方法中只比较对象的某个属性,则只要该属性相同,程序就认为两个对象是重复的。
//因此,在compareTo()方法中一般要求比较对象的所有属性,保证compareTo()的结果与hashCode()和equal()一致。
@Override
public int compareTo(People o) {
int result = this.name.compareTo(o.name);//只比较了name属性
return result;
}


五、关于TreeSet的底层实现

1 . TreeSet内部定义了一个NavigableMap类型的接口变量。在调用构造器生成TreeSet实例时,都是实例化了一个TreeMap对象,并回调给NavigableMap接口变量。因此,TreeSet底层使用的是TreeMap来存储数据,与HashSet底层使用的是HashMap类似。

private transient NavigableMap<E,Object> m;//私有NavigableMap变量

//TreeSet的其他构造方法均在内部调用该构造方法,将TreeMap实例回调给NavigableMap接口变量
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}

//内部调用带参构造方法
public TreeSet() {
this(new TreeMap<E,Object>());
}
//内部调用带参构造方法,TreeSet的其他构造方法都类似,具体可查看API
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}


六、Comparable和Comparator

Comparable可以认为是一个内比较器,一个类通过实现Comparable接口,在内部重写
compareTo()
方法,来定义如何比较该类的两个对象。

Comparator可以认为是是一个外比较器,一个类通过实现泛型接口Comparator< T >,重写
compare(T o1, T o2)
方法来定义如何比较两个 T 类的对象。实现Comparator< T >接口的类,可以认为是 T 类的一个专门的比较器类,是类 T 的一个外部比较器。

一个对象不支持自己和自己比较(没有实现Comparable接口),但是又需要对两个对象进行比较时,可以构造一个实现Comparator接口的外部比较器。比如需要将该类的对象存入Set中时。

一个对象实现了Comparable接口,但是开发者认为
compareTo()
方法中的比较方式并不是自己想要的那种比较方式,可以构造一个实现Comparator接口的外部比较器。

实现Comparable接口的方式比实现Comparator接口的耦合性要强一些,如果要修改比较算法,要修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修改。

在具体场景下选择最合适的那种比较器。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: