準(zhǔn)備知識(shí)點(diǎn):
- java中涉及到集合的接口與實(shí)現(xiàn)類都位于java.util包下
-
集合中接口繼承體系如下圖所示:
java集合接口體系.png -
集合中各個(gè)接口所對(duì)應(yīng)的主要實(shí)現(xiàn)類如下圖所示:
集合中接口所對(duì)應(yīng)的主要實(shí)現(xiàn)類.png
筆試題目一:
代碼如下,問(wèn)控制臺(tái)為什么只打印出abc、xyz?
class People {
String name;
public People(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public class HashSetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
set.add(new String("abc"));
set.add(new String("xyz"));
set.add(new String("abc"));
for (Iterator<String> ite = set.iterator(); ite.hasNext();) {
String next = ite.next();
System.out.println(next);
}
}
}
答案:因?yàn)镠ashSet集合的特性是元素唯一性,而HashSet底層是通過(guò)hashcode以及equals方法來(lái)判定對(duì)象是否唯一的,而String類覆寫(xiě)了equals方法以及hashcode方法這讓new String("abc")與new String("abc")的hashcode以及equals方法的比較都為true從而被判定是重復(fù)元素?zé)o法添加到集合中。
準(zhǔn)備知識(shí)點(diǎn):
- HashSet底層是通過(guò)HashMap來(lái)實(shí)現(xiàn)的
- HashMap底層是通過(guò)Entry數(shù)組存儲(chǔ)Entry鏈表來(lái)實(shí)現(xiàn)的
HashSet增加元素時(shí)java底層的操作流程如下:
- 調(diào)用HashMap.put方法來(lái)存儲(chǔ)key、value
- 使用hash算法對(duì)key.hashCode()計(jì)算出一個(gè)hash值
- 讓得到的hash值與數(shù)組長(zhǎng)度 - 1進(jìn)行&運(yùn)算得到Entry數(shù)組索引位置(該位置用來(lái)存儲(chǔ)Entry對(duì)象,該Entry對(duì)象封裝了所要添加的key-value對(duì))
- 得到Entry數(shù)組索引后,遍歷該索引位置所指向的Entry鏈表中的Entry元素,如果計(jì)算得到的hash值與鏈表中對(duì)象的hash值相等且(key對(duì)象是同一個(gè)對(duì)象或者key對(duì)象的內(nèi)容相等)那么當(dāng)前所添加的value值覆蓋鏈表中匹配到的元素的value值并返回老的value值,至此HashSet.add方法結(jié)束,如果遍歷了Entry鏈表后發(fā)現(xiàn)沒(méi)有一個(gè)元素符合條件那么就會(huì)將所添加的key對(duì)象與value對(duì)象封裝成一個(gè)新的Entry對(duì)象并將所計(jì)算出來(lái)的Entry數(shù)組索引指向這個(gè)新的Entry對(duì)象,而這個(gè)新的Entry對(duì)象的next屬性指向舊的鏈表,換言之將新生成的Entry對(duì)象插入到Entry數(shù)組所計(jì)算出的索引處所指向的鏈表頭部。
總結(jié):當(dāng)向集合HashSet中增加對(duì)象時(shí),首先集合計(jì)算要增加對(duì)象的hashCode碼,根據(jù)該值來(lái)得到一個(gè)Entry數(shù)組索引位置用來(lái)存放當(dāng)前的對(duì)象,當(dāng)在該位置沒(méi)有一個(gè)對(duì)象的話,那么HashSet認(rèn)為該對(duì)象在集合中不存在,直接增加進(jìn)去。如果在該位置有一個(gè)Entry對(duì)象存在的話,接著將準(zhǔn)備增加到集合中的對(duì)象與該位置上的Entry鏈表中的各個(gè)Entry對(duì)象進(jìn)行equals方法和hashcode值比較,如果迭代完畢后發(fā)現(xiàn)沒(méi)有一個(gè)Entry對(duì)象的equals方法以及hashcode值與新增對(duì)象相符(注:即比較結(jié)果為true),那么集合認(rèn)為集合中不存在該對(duì)象,直接將該對(duì)象封裝成Entry對(duì)象并將其做為數(shù)組索引處所指向的Entry鏈表的頭部元素,如果在迭代過(guò)程中發(fā)現(xiàn)有元素與新增的元素的equals比較以及hashcode值得比較都為true,那么集合認(rèn)為集合中已經(jīng)存在該對(duì)象了,不會(huì)再將該對(duì)象增加到集合中去了(注:不過(guò)對(duì)于HashMap它會(huì)將對(duì)象的value值替換匹配到的對(duì)象的value值并將舊的value值返回)
筆試題目二:
代碼如下,控制臺(tái)會(huì)輸出什么,并進(jìn)行相應(yīng)的解釋:
class People {
String name;
public People(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public class HashSetTest {
public static void main(String[] args) {
Set<People> set = new HashSet<People>();
set.add(new People("zhangsan"));
set.add(new People("lisi"));
set.add(new People("zhangsan"));
for (Iterator<People> ite = set.iterator(); ite.hasNext();) {
People next = ite.next();
System.out.println(next.getName());
}
}
}
答案:控制臺(tái)會(huì)輸出zhangsan、lisi、zhangsan。因?yàn)镻eople類沒(méi)有覆寫(xiě)equals以及hashcode方法,從而導(dǎo)致HashSet底層判定它們?yōu)椴煌兀罱K被存儲(chǔ)到了集合中。
筆試題目三:
代碼如下,控制臺(tái)會(huì)輸出什么?為什么?
class People {
String name;
public People(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (null == obj) return false;
if (obj instanceof People) {
People p = (People)obj;
return p.name.equals(this.name);
}
return false;
}
@Override
public int hashCode() {
return this.name.hashCode();
}
}
public class HashSetTest {
public static void main(String[] args) {
Set<People> set = new HashSet<People>();
set.add(new People("zhangsan"));
set.add(new People("lisi"));
set.add(new People("zhangsan"));
for (Iterator<People> ite = set.iterator(); ite.hasNext();) {
People next = ite.next();
System.out.println(next.getName());
}
}
}
答案:控制臺(tái)會(huì)輸出lisi、zhangsan。因?yàn)镻eople類重寫(xiě)了equals方法以及hashcode方法,從而在HashSet底層new People("zhangsan")與new People("zhangsan")被判定為重復(fù)元素,故無(wú)法添加到集合中。
知識(shí)點(diǎn)羅列:
- 當(dāng)重寫(xiě)equals方法的時(shí)候必須重寫(xiě)hashcode方法 (HashMap/HashSet都需要用到這個(gè)特性,否則這些對(duì)象要存儲(chǔ)于HashMap/HashSet集合中的時(shí)候會(huì)出現(xiàn)重復(fù)元素存儲(chǔ)的問(wèn)題,即無(wú)法維持元素的唯一性。)
- 如果一個(gè)類的兩個(gè)對(duì)象進(jìn)行equals方法比較的時(shí)候返回true,那么這兩個(gè)對(duì)象必須具有相同的hashcode。(否則存儲(chǔ)到HashMap/HashSet里的時(shí)候還是無(wú)法維持元素的唯一性。注:判定元素重復(fù)的條件是:hashcode以及equals方法都返回true)
- transient修飾的變量無(wú)法被序列化到文件中,信息會(huì)丟失
- HashMap對(duì)于key為null的存儲(chǔ)規(guī)則是將它存儲(chǔ)在HashMap所維護(hù)的Entry數(shù)組的第一個(gè)元素的鏈表中(即:key為null的key-value永遠(yuǎn)存儲(chǔ)在數(shù)組的第一個(gè)元素的鏈表中)
- java約定用關(guān)鍵字final修飾的常量名要大寫(xiě)
- 對(duì)于HashSet、HashMap來(lái)說(shuō),這樣做就是為了提高查找效率,使得查找時(shí)間不隨著set或者map的大小而改變
- HashMap所維護(hù)的Entry數(shù)組的默認(rèn)長(zhǎng)度是16
- HashMap負(fù)載因子默認(rèn)為0.75f(根據(jù)經(jīng)驗(yàn)得到,數(shù)組現(xiàn)存儲(chǔ)元素個(gè)數(shù)/數(shù)組長(zhǎng)度),主要是降低沖突的可能性,當(dāng)超過(guò)該負(fù)載因子的時(shí)候會(huì)擴(kuò)容數(shù)組,不讓數(shù)組中的Entry鏈表長(zhǎng)度過(guò)長(zhǎng)而違背HashMap設(shè)計(jì)的初衷

