List接口——Collection子接口
1. 主要實現類和方法
1)主要實現類: HashSet
2) 常用方法:
Set接口中聲明的方法都是Collection接口聲明過的。HashSet能使用的就是Collection中定義的方法。
@Test
public void test1(){
Set set = new HashSet();
set.add(223);
set.add(new String("AA"));
set.add("CC");
set.add(223);
set.add(new String("AA"));
set.add(new Person("Tom",12));
set.add(new Person("Tom",12));
set.add(null);
for(Object obj : set){
System.out.println(obj);
}
}
2. Set的特性:無序性、不可重複性
1) 無序性:
不等同於隨機性!元素在底層儲存的位置不是像數組一樣是依次緊密排列的,而是參考其hashCode值決定的存儲位置。無序指底層存儲無序。
2) 不可重複性:
根據對象的equals()進行判斷。如果返回true,則添加失敗。保證了不可重複性。
3. HashSet
1) HashSet源碼解析
底層採用HashMap存儲元素
public HashSet() {
map = new HashMap<>();
}
key存放set的元素,value存放object對象
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public void clear() {
map.clear();
}
2) 向HashSet中添加數據的過程 (HashSet底層使用數組+鏈表+(jdk8:紅黑樹)存儲)
-
① 將元素e1添加到HashSet中,首先調用e1所在類的hashCode(),獲取e1對象的哈希值。
-
② 此哈希值,經過某種算法以後,獲取其在HashSet底層數組中的存放位置。
-
③ 如果此位置上,沒有其他任何元素,則e1添加成功 —>情況1
如果此位置上,已經存在某個或某幾個元素e2,則繼續判斷。 -
④ 比較e1和e2的哈希值,如果兩個哈希值不相同。則e1添加成功。 —>情況2
比較e1和e2的哈希值,如果兩個哈希值相同,則調用e1所在類的equals()方法 -
⑤ equals()方法返回false,則e1添加成功。 —>情況3
equals()方法返回true,則e1添加失敗。
3) 向HashSet中添加元素對添加的元素所屬的類的要求
- 針對於HashSet或者LinkedHashSet來說,如果多個對象需要存儲到上述兩個Set中時,爲了保證不可重複性,以及根據對源碼的分析,必須要求對象所屬的類要重寫hashCode()和equals()。
- 重寫hashCode()和equals()要保證一致性!相等的對象必須具有相等的散列碼(哈希值)
4) HashSet擴容機制
縱向和橫向的變化,如果一個位置的鏈表長度達到8,但是整個數組的數量還不到64,就對這個數組進行擴容;
如果這個數組已經超過64了,而且這個位置鏈表還超過8了,就會轉化爲樹
4. Set的不同實現類的對比
|-----Collection:存儲一個一個的數據
|-----Set:存儲無序的、不可重複的數據: 高中的集合
|-----HashSet:是Set的主要實現類;線程不安全的;可以存儲null值
|-----LinkedHashSet:是HashSet的子類;在添加數據之外,
還通過一對指針記錄先後添加的順序,
使得遍歷Set元素時,較HashSet效率更高。
|-----TreeSet:可以按照添加的元素的指定的屬性的大小進行遍歷;
底層使用的是紅黑樹(排序二叉樹的一種)
5. LinkedHashSet的測試(瞭解)
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
public LinkedHashSet() {
super(16, .75f, true);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
@Test
public void test2(){
Set set = new LinkedHashSet();
set.add(223);
set.add(new String("AA"));
set.add("CC");
set.add(223);
set.add(new String("AA"));
set.add(new Person("Tom",12));
set.add(new Person("Tom",12));
set.add(null);
for(Object obj : set){
System.out.println(obj);//輸出結果的順須和HashSet對比,應該是不同的。
}
}
6. TreeSet添加數據的情況 (瞭解)
1) TreeSet可以按照添加的元素的指定的屬性的大小進行遍歷;
2) 排序的方式有:① 自然排序 ② 定製排序
3) TreeSet底層使用的是紅黑樹(排序二叉樹的一種)
4) 要求:向TreeSet中添加的元素必須是同一個類型的對象。
5) 說明:TreeSet中不能存放相同的元素。
判斷的標準不再是元素所在類的hashCode()和equals()了。
而是按照自然排序或定製排序中重寫的compareTo()或compare()進行比較。
如果compareTo或compare返回0,則代表相同,無法添加;
如果返回正數,則代表兩個對象的前者比後者大;
如果返回負數,則代表兩個對象的前者比後者小。
例子1
自然排序(通過元素所屬類實現的Comparable接口)
@Test
public void test1(){
TreeSet set = new TreeSet();
set.add("CC");
set.add("MM");
set.add("GG");
set.add("TT");
set.add("JJ");
set.add("KK");
// set.add(123);//報ClassCastException
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
輸出結果:
CC
GG
JJ
KK
MM
TT
例子2
自然排序
@Test
public void test4(){
TreeSet<Integer> integers = new TreeSet<>(com);
integers.add(2);
integers.add(22);
integers.add(25);
integers.add(12);
integers.add(7);
System.out.println(integers);
}
輸出結果:
[2, 7, 12, 22, 25]
定製排序
@Test
public void test4(){
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return -o1.compareTo(o2);
}
};
TreeSet<Integer> integers = new TreeSet<>(com);
integers.add(2);
integers.add(22);
integers.add(25);
integers.add(12);
integers.add(7);
System.out.println(integers);
}
輸出結果:
[25, 22, 12, 7, 2]
例子3
Person類的定義
public class Person implements Comparable{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
*/
//先按照年齡從小到大排列,再按照姓名從小到大排
@Override
public int compareTo(Object o) {
if(o == null) return -1;
if(o == this) return 1;
if(o instanceof Person){
Person p = (Person)o;
int value = this.age - p.age;
if(value != 0){
return value;
}else{
return this.name.compareTo(p.name);
}
}
throw new RuntimeException("輸入的類型不匹配");
}
}
自然排序
@Test
public void test2(){
TreeSet set = new TreeSet();
Person p1 = new Person("Tom",12);
Person p2 = new Person("Jim",32);
Person p3 = new Person("Jerry",26);
Person p4 = new Person("Mike",43);
Person p5 = new Person("Lily",26);
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
set.add(p5);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
定製排序:當Compareable接口和Comaprator接口都存在時,優先按照Comparator接口的規則排序
@Test
public void test3(){
Comparator com = new Comparator(){
@Override
public int compare(Object o1, Object o2) {//o1,o2應該爲Person的實例
if(o1 instanceof Person && o2 instanceof Person){
Person p1 = (Person)o1;
Person p2 = (Person)o2;
return -p1.getName().compareTo(p2.getName());
}
return 0;
}
};
TreeSet set = new TreeSet(com);
Person p1 = new Person("Tom",12);
Person p2 = new Person("Jim",32);
Person p3 = new Person("Jerry",26);
Person p4 = new Person("Mike",43);
Person p5 = new Person("Lily",26);
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
set.add(p5);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}