HashSet
HashSet實作了 Set 接口,底層是一個HashMap。源碼如下:
public class HashSet<E>
{
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<>();
}
}
通過以上源碼可以發現,建立一個HashSet對象實際上是建立了一個HashMap對象。
HashSet中的元素是無序的。這裡的無序指的是進出集合的順序。如下:
public class HashSetTest {
public static void main(String[] args) {
HashSet<String> hashSet = new HashSet<>();
hashSet.add("1.Java");
hashSet.add("2.HashSet");
hashSet.add("3.元素");
hashSet.add("4.進出");
hashSet.add("5.無序");
// 使用增強for循環變量元素
for(String str : hashSet){
System.out.println(str);
}
}
}
周遊結果為:
4.進出
2.HashSet
3.元素
1.Java
5.無序
從周遊的結果來看,元素周遊時的順序和添加時的順序不同。
HashSet中的元素是唯一的。多次添加相同的元素,隻會有一個元素加入HashSet。通過檢視HashSet中add方法的源碼發現,這依賴于底層的hashCode方法和equals方法。如下:
// HashSet中的add方法,其中又調用了HashMap中的put方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
從以上可以看出: add方法隻是作了一個判斷,添加元素真正起作用的還是HashMap中的put方法,元素是作為key存儲的。
// HashMap中的put方法,這裡的key是HashSet中的元素
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash==hash && ((k=e.key)==key||key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
// put方法中調用的hash方法,傳回值與key(即元素)的hashCode方法有關
final int hash(Object k) {
int h = hashSeed;
if ( != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> ) ^ (h >>> );
return h ^ (h >>> ) ^ (h >>> );
}
從put方法的源碼中可以知道,判斷元素是否唯一,取決于表達式e.hash==hash && ((k=e.key)==key||key.equals(k))的值。其中hash值的計算與元素的hashCode方法有關;而((k=e.key)==key||key.equals(k)) 與元素的equals方法有關。如果元素的hash值相同,且元素相等(對于基礎資料類型而言)或内容相同(對于引用類型而言),表達式結果為true,說明HashSet中已有這個元素,則不會繼續添加該元素。綜上,說明HashSet中元素的唯一性與元素中的hashCode方法和equals方法有關。要保證HashSet中元素的唯一性,就需要保證這兩個方法正常判斷。是以,當HashSet中的元素為自定義類的對象時,自定義類就需要重寫hashCode方法和equals方法。如下:
public class Student {
private String name;
private int age;
private String sex;
Student(){
}
Student(String name, int age, String sex){
this.name = name;
this.age = age;
this.sex = sex;
}
public int hashCode() {
final int prime = ;
int result = ;
result = prime * result + age;
result = prime * result +
((name == null) ? : name.hashCode());
result = prime * result +
((sex == null) ? : sex.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (sex == null) {
if (other.sex != null)
return false;
} else if (!sex.equals(other.sex))
return false;
return true;
}
}
TreeSet
TreeSet的底層是TreeMap。如下:
public class TreeSet<E>
{
private transient NavigableMap<E,Object> m;
// 這裡的NavigableMap是接口
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
//TreeMap實作了NavigableMap接口
public TreeSet() {
this(new TreeMap<E,Object>());
}
}
TreeSet的元素很有特點,元素是唯一的,而且集合會根據元素的自然順序對元素排序,但元素的進出仍然是無序的。如下:
public class TreeSetTest {
public static void main(String[] args) {
TreeSet<Integer> treeSet = new TreeSet();
// 添加元素
treeSet.add();
treeSet.add();
treeSet.add();
treeSet.add();
treeSet.add();
treeSet.add();
treeSet.add();
// 周遊元素
for(Integer number : treeSet){
System.out.print(number + );
}
}
}
周遊結果為:
1 3 4 5 7
這說明TreeSet中的元素不重複,按自然順序排序,且進出無序。實際上,TreeSet中元素的自然排序和元素的唯一性依賴于compareTo或compare方法。如下:
//TreeSet 中的add方法,元素仍然是作為key 存儲的
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
// TreeMap 中的put方法
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key);
root = new Entry<>(key, value, null);
size = ;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
// TreeSet中的構造TreeSet(Comparator<? super E> comparator)
// 實際上是調用了TreeMap中的同參構造方法,即:
// TreeMap(Comparator<? super K> comparator)
// 此時,指定了comparator(排序比較器),就按比較器的規則進行比較
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < )
t = t.left;
else if (cmp > )
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
// TreeSet中的無參構造方法,實際上是調用TreeMap的空參構造方法
// 此時沒有指定comparator(排序比較器),comparator 為null,
// 比較時調用元素中的compareTo方法
else {
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < )
t = t.left;
else if (cmp > )
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < )
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
從以上源碼可知:TreeSet在添加元素時,會對元素進行自然順序的比較。如果TreeSet的構造方法中指定了排序比較器comparator,元素的比較就使用排序比較器中的compare方法;如果沒有指定,元素的比較就使用元素中的compareTo方法。是以,要在TreeSet 中添加自定義類的對象作為元素時,要麼在TreeSet的構造方法中指定排序比較器;要麼在自定義類中實作Comparable接口,重寫compareTo方法。如下:
// 重寫compareTo方法
public class Person implements Comparable<Person>
{
private String name;
private String sex;
private int age;
Person(String name, String sex, int age){
this.name = name;
this.sex = sex;
this.age = age;
}
public int compareTo(Person p) {
int result1 = this.name.compareTo(p.name);
int result2 = this.sex.compareTo(p.sex);
int result3 = this.age - p.age;
return result1== ?
(result2== ?
(result3== ? result3 : result3)
: result2 ) : result1;
}
}
或者指定比較器comparator
public class TreeSetTest {
public static void main(String[] args) {
// 建立TreeSet對象時,在構造方法中使用匿名類的方式指定comparator
TreeSet<Person> treeSet =
new TreeSet<>(new Comparator<Person>(){
public int compare(Person p1, Person p2) {
int result1 = p1.getName().compareTo(
p2.getName());
int result2 = p1.getSex().compareTo(p2.getSex());
int result3 = p1.getAge() - p2.getAge();
return result1== ?
(result2== ?
(result3== ? result3 : result3)
: result2 ) : result1;
}
});
treeSet.add(new Person("John","male",));
treeSet.add(new Person("Michle","male",));
treeSet.add(new Person("Lucy","female",));
treeSet.add(new Person("John","male",));
treeSet.add(new Person("Lucy","female",));
treeSet.add(new Person("Adam","male",));
for(Person person : treeSet){
System.out.println(person + " ");
}
}
}