Java集合面試總結
★★★★★集合框架:用於儲存資料的容器。
特點:
1:物件封裝資料,物件多了也需要儲存。集合用於儲存物件。
2:物件的個數確定可以使用陣列,但是不確定怎麼辦?可以用集合。因為集合是可變長度的。
集合和陣列的區別:
1:陣列是固定長度的;集合可變長度的。
2:陣列可以儲存基本資料型別,也可以儲存引用資料型別;集合只能儲存引用資料型別。
3:陣列儲存的元素必須是同一個資料型別;集合儲存的物件可以是不同資料型別。
資料結構:就是容器中儲存資料的方式。
對於集合容器,有很多種。因為每一個容器的自身特點不同,其實原理在於每個容器的內部資料結構不同。
集合容器在不斷向上抽取過程中。出現了集合體系。
在使用一個體系時,原則:參閱頂層內容。建立底層物件。
1 Iterator介面
1.1 Iterator
< java.util >-- 迭代器:是一個介面—Iterator介面,其作用:用於取集合中的元素。
在Iterator介面中定義了三個方法:
|
|
next |
|
|
|
每一個集合都有自己的資料結構(就是容器中儲存資料的方式),都有特定的取出自己內部元素的方式。為了便於操作所有的容器,取出元素。將容器內部的取出方式按照一個統一的規則向外提供,這個規則就是
也就說,只要通過該介面就可以取出Collection集合中的元素,至於每一個具體的容器依據自己的資料結構,如何實現的具體取出細節,這個不用關心,這樣就降低了取出元素和具體集合的耦合性。
Iterator it = coll.iterator();//獲取容器中的迭代器物件,至於這個物件是是什麼不重要。這物件肯定符合一個規則Iterator介面。
public static voidmain(String[] args) { Collection coll = new ArrayList(); coll.add("abc0"); coll.add("abc1"); coll.add("abc2"); //--------------方式1---------------------- Iterator it = coll.iterator(); while(it.hasNext()){ System.out.println(it.next()); } //---------------方式2用此種---------------------- for(Iterator it =coll.iterator();it.hasNext(); ){ System.out.println(it.next()); } }
使用Iterator迭代器來進行刪除,則不會出現併發修改異常。
因為:在執行remove操作時,同樣先執行checkForComodification(),然後會執行ArrayList的remove()方法,該方法會將modCount值加1,這裡我們將expectedModCount=modCount,使之保持統一。
1.2 ListIterator
上面可以看到,Iterator只提供了刪除元素的方法remove,如果我們想要在遍歷的時候新增元素怎麼辦?
ListIterator介面繼承了Iterator介面,它允許程式設計師按照任一方向遍歷列表,迭代期間修改列表,並獲得迭代器在列表中的當前位置。
使用ListIterator來對list進行邊遍歷邊新增元素操作:
public static void main(String[] args) { ArrayList<String> aList = new ArrayList<String>(); aList.add("bbc"); aList.add("abc"); aList.add("ysc"); aList.add("saa"); System.out.println("移除前:" + aList); ListIterator<String> listIt = aList.listIterator(); while(listIt.hasNext()) { if("abc".equals(listIt.next())) { listIt.add("haha"); } } System.out.println("移除後:" + aList); }
2 Collection介面
--< java.util >--Collection介面:
Collection:
|--List:有序(元素存入集合的順序和取出的順序一致),元素都有索引。元素可以重複。
|--Set:無序(存入和取出順序有可能不一致),不可以儲存重複元素。必須保證元素唯一性。
1.新增:
add(object):新增一個元素
addAll(Collection) :新增一個集合中的所有元素。
2.刪除:
clear():將集合中的元素全刪除,清空集合。
remove(obj) :刪除集合中指定的物件。注意:刪除成功,集合的長度會改變。
removeAll(collection) :刪除部分元素。部分元素和傳入Collection一致。
3.判斷:
boolean contains(obj) :集合中是否包含指定元素 。
boolean containsAll(Collection) :集合中是否包含指定的多個元素。
boolean isEmpty():集合中是否有元素。
4.獲取:
int size():集合中有幾個元素。
5.取交集:
boolean retainAll(Collection) :對當前集合中保留和指定集合中的相同的元素。如果兩個集合元素相同,返回flase;如果retainAll修改了當前集合,返回true。
6.獲取集合中所有元素:
Iterator iterator():迭代器
7.將集合變成陣列:
toArray();
2.1 List介面
--< java.util >-- List介面:
List本身是Collection介面的子介面,具備了Collection的所有方法。現在學習List體系特有的共性方法,查閱方法發現List的特有方法都有索引,這是該集合最大的特點。
List:有序(元素存入集合的順序和取出的順序一致)元素都有索引。元素可以重複。(有序可重複)
|--ArrayList:底層的資料結構是陣列,執行緒不同步,ArrayList替代了Vector,查詢元素的速度非常快。預設大小10,1.5倍長度擴容。
|--LinkedList:底層的資料結構是連結串列,執行緒不同步,增刪元素的速度非常快。
|--Vector:底層的資料結構就是陣列,執行緒同步,Vector無論查詢和增刪都巨慢。預設大 小10,2倍長度擴容。
1.新增:
add(index,element) :在指定的索引位插入元素。
addAll(index,collection) :在指定的索引位插入一堆元素。
2.刪除:
remove(index) :刪除指定索引位的元素。 返回被刪的元素。
3.獲取:
Object get(index) :通過索引獲取指定元素。
int indexOf(obj):獲取指定元素第一次出現的索引位,如果該元素不存在返回-1;
所以,通過-1,可以判斷一個元素是否存在。
int lastIndexOf(Object o) :反向索引指定元素的位置。
List subList(start,end):獲取子列表。
4.修改:
Object set(index,element) :對指定索引位進行元素的修改。
5.獲取所有元素:
ListIterator listIterator():list集合特有的迭代器。
List集合支援對元素的增、刪、改、查。
List集合因為角標有了自己的獲取元素的方式: 遍歷。
for(int x=0;x<list.size(); x++){
sop("get:"+list.get(x));
}
在進行list列表元素迭代的時候,如果想要在迭代過程中,想要對元素進行操作的時候,比如滿足條件新增新元素。會發生.ConcurrentModificationException併發修改異常。
導致的原因是:
集合引用和迭代器引用在同時操作元素,通過集合獲取到對應的迭代器後,在迭代中,進行集合引用的元素新增,迭代器並不知道,所以會出現異常情況。
如何解決呢?
既然是在迭代中對元素進行操作,找迭代器的方法最為合適.可是Iterator中只有hasNext,next,remove方法.通過查閱的它的子介面,ListIterator,發現該列表迭代器介面具備了對元素的增、刪、改、查的動作。
ListIterator是List集合特有的迭代器。
ListIterator it =list.listIterator;//取代Iterator it = list.iterator;
方法摘要 |
|
|
|
|
|
|
|
next |
|
|
|
|
|
|
|
|
|
|
可變長度陣列的原理:
當元素超出陣列長度,會產生一個新陣列,將原陣列的資料複製到新陣列中,再將新的元素新增到新陣列中。
ArrayList:是按照原陣列的50%延長。構造一個初始容量為 10 的空列表。
Vector:是按照原陣列的100%延長。
注意:對於list集合,底層判斷元素是否相同,其實用的是元素自身的equals方法完成的。所以建議元素都要複寫equals方法,建立元素物件自己的比較相同的條件依據。
LinkedList:的特有方法。
addFirst();
addLast();
在jdk1.6以後。
offerFirst();
offerLast();
getFirst():獲取連結串列中的第一個元素。如果連結串列為空,丟擲NoSuchElementException;
getLast();獲取連結串列中的最後一個元素。如果連結串列為空,丟擲NoSuchElementException;
在jdk1.6以後。
peekFirst();獲取連結串列中的第一個元素。如果連結串列為空,返回null。
peekLast();
removeFirst():獲取連結串列中的第一個元素,但是會刪除連結串列中的第一個元素。如果連結串列為空,丟擲NoSuchElementException
removeLast();
在jdk1.6以後。
pollFirst();獲取連結串列中的第一個元素,但是會刪除連結串列中的第一個元素。如果連結串列為空,返回null。
pollLast();
2.2 Set介面
< java.util >-- Set集合無序,add()相同元素則新增失敗,返回flase。:
資料結構:資料的儲存方式;
Set介面中的方法和Collection中方法一致的。Set介面取出方式只有一種,迭代器。
|--HashSet:底層資料結構是雜湊表,HashSet是集合,無序,高效,執行緒不同步。
|--LinkedHashSet:存取順序一致(用連結串列維護),執行緒不同步,是hashset的子類。
|--TreeSet:元素唯一,有序(按照元素自身執行順序),執行緒不同步(不按原有陣列的順序)。TreeSet底層的資料結構就是二叉樹(平衡二叉排序樹)。
|--EnumSet 只能儲存同一型別元素。
HashSet、TreeSet、LinkedHashSet的區別:HashSet只去重,TreeSet去重並排序,LinkedHashSet去重並保留插入順序
===HashSet 雜湊表原理===
採用雜湊表儲存結構。
1:對物件元素中的關鍵字進行雜湊演算法運算,得結果為雜湊值(也是這個元素的位置)。
2:儲存雜湊值的結構,我們稱為雜湊表,在雜湊表中查詢對應的雜湊值對應位置,
3:如果雜湊值出現衝突,再次判斷這個關鍵字對應的物件是否相同:
如果物件相同,就不儲存,因為元素重複;
如果物件不同,就儲存,在原來物件的雜湊值基礎 +1順延。
4:既然雜湊表根據雜湊值儲存,為提高效率,最好保證物件關鍵字的唯一性。 可儘量少的判斷關鍵字對應的物件是否相同,提高了雜湊表的操作效率。
高效:保證關鍵字唯一性,即為上述第三步所述,也可以用以下敘述:
HashSet集合保證元素唯一性:通過元素的hashCode()和equals()完成的。
當元素的hashCode值相同時,才繼續判斷元素的equals是否為true。
如果為true,那麼視為相同元素,不存。如果為false,那麼儲存。
如果hashCode值不同,那麼不判斷equals,從而提高物件比較的速度。
對於ArrayList集合,判斷元素是否存在,或者刪元素底層依據都是equals方法。
對於HashSet集合,判斷元素是否存在,或者刪除元素,底層依據的是hashCode方法和equals方法。
===TreeSet原理===
採用二叉樹(二叉平衡排序樹)儲存結構 (或紅黑樹)
TreeSet用於對Set集合進行元素的指定順序排序,要依據元素自身的比較性( 如果元素不具備比較性,在執行時會發生ClassCastException異常)所以需要元素實現Comparable介面,複寫compareTo方法(根據指定需求),強制讓物件元素具備比較性,否則比較時引發ClassCastException異常。
TreeSet支援兩種排序方法:自然排序和定製排序;預設採用自然排序。
原理:當把一個物件添(必須實現Comparable介面)加進TreeSet時,TreeSet呼叫該物件的compareTo(Objectobj)方法與容器中的其他物件比較大小,然後根據紅黑樹演算法決定它的儲存位置。 如果兩個物件通過compareTo(Object obj)比較相等,return0,視為兩物件重複,不儲存。(通過此方法保證了物件的唯一性)
注意:在進行比較時,如果判斷元素不唯一,比如,同姓名,同年齡,才視為同一個人。
在判斷時,需要分主要、次要條件,當主要條件相同時,再判斷次要條件,按照次要條件排序。
TreeSet集合排序有兩種方式,Comparable和Comparator區別:
1:讓元素自身具備比較性,需要元素物件實現Comparable介面,覆蓋compareTo方法。
2:讓集合自身具備比較性,需要定義一個實現了Comparator介面的比較器,並覆蓋compare方法,並將該類物件作為實際引數傳遞給TreeSet集合的建構函式。第二種方式較為靈活。
3 Map介面
Map
|--Hashtable:底層是雜湊散列表資料結構,執行緒同步。不可以儲存null鍵,null值。不可序列化,使用bucket結構體表示單個元素,使用雙重雜湊法(閉雜湊法)解決衝突(二度雜湊,size>length時要進行模運算)。
|--HashMap:底層是雜湊表資料結構(鏈地址法解決衝突),執行緒不同步。可存一個null鍵和多個null值。替代了Hashtable. 但可通過Map m = Collections.synchronizeMap(hashMap)實現同步;
|--LinkedHashMap,採用雙向連結串列資料結構連線起來所有的entry,保證了存入和取出順序一致,即連結串列有序;執行緒不同步。
|--TreeMap:底層是二叉樹結構(平衡二叉排序樹),可以對map集合中的鍵進行指定順序的排序。
Map集合儲存和Collection有著很大不同:
Collection一次存一個元素,是單列集合;
Map一次存一對元素,是雙列集合。Map儲存的一對元素:鍵--值,鍵(key)與值(value)間有對應(對映)關係。
特點:要保證Map中鍵的唯一性。
1:新增。
put(key,value):當儲存的鍵相同時,新的值會替換老的值,並將老值返回。如果鍵沒有重複,返回null。
void putAll(Map);
2:刪除。
void clear():清空
value remove(key) :刪除指定鍵。
3:判斷。
boolean isEmpty():
boolean containsKey(key):是否包含key
boolean containsValue(value):是否包含value
4:取出。
int size():返回長度
value get(key) :通過指定鍵獲取對應的值。如果返回null,可以判斷該鍵不存在。當然有特殊情況,就是在hashmap集合中,是可以儲存null鍵null值的。
Collection values():獲取map集合中的所有的值。
5:想要獲取map中的所有元素
原理:map沒有迭代器,collection具備迭代器,只要將map轉成Set集合,就可使用迭代器。之所以轉成set,是因為map集合具備鍵的唯一性,其實set集合就來自於map,set集合底層其實用的就是map的方法。
★ 把map集合轉成set的方法:(決定了兩種遍歷方式)
Set keySet();
Set entrySet();//取的是鍵和值的對映關係。
Entry就是Map介面中的內部介面;
為什麼要定義在map內部呢?entry是訪問鍵值關係的入口,是map的入口,訪問的是map中的鍵值對。
---------------------------------------------------------
取出map集合中所有元素的方式一:keySet()方法。
可以將map集合中的鍵都取出存放到set集合中。對set集合進行迭代。迭代完成,再通過get方法對獲取到的鍵進行值的獲取。
Set keySet = map.keySet(); Iterator it = keySet.iterator(); while(it.hasNext()) { Object key = it.next(); Objectvalue = map.get(key); System.out.println(key+":"+value); }
取出map集合中所有元素的方式二:entrySet()方法。
Set entrySet = map.entrySet(); Iterator it =entrySet.iterator(); while(it.hasNext()) { Map.Entry me =(Map.Entry)it.next(); System.out.println(me.getKey()+"::::"+me.getValue()); }
使用集合的技巧:
看到Array就是陣列結構,有角標,查詢速度很快。
看到link就是連結串列結構:增刪速度快,而且有特有方法。addFirst; addLast;removeFirst(); removeLast();getFirst();getLast();
看到hash就是雜湊表,就要想要雜湊值,就要想到唯一性,就要想到存入到該結構中的元素必須覆蓋hashCode和equals方法。
看到tree就是二叉樹,就要想到排序,就想要用到比較。
比較的兩種方式:
一個是Comparable:覆蓋compareTo方法;
一個是Comparator:覆蓋compare方法。
LinkedHashSet,LinkedHashMap:這兩個集合可以保證雜湊表有存入順序和取出順序一致,保證雜湊表有序。
集合使用場景?
當儲存一個元素時,用Collection。當儲存物件之間存在著對映關係時,用Map集合。
保證唯一,就用Set。不保證唯一,就用List。
4 綜合總結
4.1 集合工具Collections
Collections:集合工具類,它的出現給集合操作提供了更多的功能。這個類不需要建立物件,內部提供的都是靜態方法。
靜態方法:
Collections.sort(list);//list集合進行元素的自然順序排序。
Collections.sort(list,new ComparatorByLen());//按指定的比較器方法排序。
class ComparatorByLen implements Comparator<String>{
public int compare(String s1,String s2){
int temp = s1.length()-s2.length();
return temp==0?s1.compareTo(s2):temp;
}
}
Collections.max(list);//返回list中字典順序最大的元素。
int index = Collections.binarySearch(list,"zz");//二分查詢,返回角標。
Collections.reverseOrder();//逆向反轉排序。
Collections.shuffle(list);//隨機對list中的元素進行位置的置換。
將非同步集合轉成同步集合的方法:Collections中的 XXX synchronizedXXX(XXX);
List synchronizedList(list);
Map synchronizedMap(map);
原理:定義一個類,將集合所有的方法加同一把鎖後返回。
Collection 和 Collections的區別:
Collections是個java.util下的類,是針對集合類的一個工具類,提供一系列靜態方法,實現對集合的查詢、排序、替換、執行緒安全化(將非同步的集合轉換成同步的)等操作。
Collection是個java.util下的介面,它是各種集合結構的父介面,繼承於它的介面主要有Set和List,提供了關於集合的一些操作,如插入、刪除、判斷一個元素是否其成員、遍歷等。
4.2 陣列 Arrays
用於運算元組物件的工具類,裡面都是靜態方法。
陣列=》集合:asList方法,將陣列轉換成list集合。
String[] arr ={"abc","kk","qq"};
List<String> list =Arrays.asList(arr);//將arr陣列轉成list集合。
將陣列轉換成集合,有什麼好處呢?用aslist方法,將陣列變成集合;
可以通過list集合中的方法來運算元組中的元素:isEmpty()、contains、indexOf、set;
注意(侷限性):陣列是固定長度,不可以使用集合物件增加或者刪除等,會改變陣列長度的功能方法。比如add、remove、clear。(會報不支援操作異常UnsupportedOperationException);
如果陣列中儲存的引用資料型別,直接作為集合的元素可以直接用集合方法操作。
如果陣列中儲存的是基本資料型別,asList會將陣列實體作為集合元素存在。
集合=》陣列:用的是Collection介面中的toArray()方法;
如果給toArray傳遞的指定型別的資料長度小於了集合的size,那麼toArray方法,會自定再建立一個該型別的資料,長度為集合的size。
如果傳遞的指定的型別的陣列的長度大於了集合的size,那麼toArray方法,就不會建立新陣列,直接使用該陣列即可,並將集合中的元素儲存到陣列中,其他為儲存元素的位置預設值null。
所以,在傳遞指定型別陣列時,最好的方式就是指定的長度和size相等的陣列。
將集合變成陣列後有什麼好處?限定了對集合中的元素進行增刪操作,只要獲取這些元素即可。
4.3 LinkedHashSet和LinkedHashMap比較
兩者實現相同,只是前者對後者做了一層包裝,即LinkedHashSet裡面有一個LinkedHashMap(介面卡模式)。下面說其實現。
LinkedHashMap,可存null鍵null值,從名字上可以看出是linkedlist和HashMap的混合體,同時滿足HashMap和linked list的某些特性。可將LinkedHashMap看作採用linked list增強的HashMap。
事實上LinkedHashMap是HashMap的直接子類,LinkedHashMap在HashMap的基礎上採用雙向連結串列(doubly-linked list)的形式將所有entry連線起來,保證元素的迭代順序跟插入順序相同。
除了迭代順序不變,還有一個好處:迭代時不需要遍歷整個table,只需要直接遍歷header指標指向的雙向連結串列即可,(LinkedHashMap的迭代時間就只跟entry的個數相關,而跟table的大小無關。)
有兩個引數可以影響LinkedHashMap的效能:初始容量(initalcapacity)和負載係數(load factor)。初始容量指定了初始table的大小,負載係數用來指定自動擴容的臨界值。當entry的數量超過capacity*load_factor時,容器將自動擴容並重新雜湊。對於插入元素較多的場景,將初始容量設大可以減少重新雜湊的次數。
向LinkedHashMap或LinkedHashSet新增物件時,需要關心兩個方法:hashCode()方法決定了物件會被放到哪個bucket裡,當多個物件的雜湊值衝突時,equals()方法決定了這些物件是否是“同一個物件”。此時需要將自定義的物件 *@Override*hashCode()和equals()方法。
5 Java集合常見題目
1.Java集合類框架的基本介面有哪些?
Java集合類提供了一套設計良好的支援對一組物件進行操作的介面和類。Java集合類裡面最基本的介面有:
Collection:代表一組物件,每一個物件都是它的子元素。
Set:不包含重複元素的Collection。
List:有順序的collection,並且可以包含重複元素。
Map:可以把鍵(key)對映到值(value)的物件,鍵不能重複。
2.為什麼集合類沒有實現Cloneable和Serializable介面?
集合類介面指定了一組叫做元素的物件。集合類介面的每一種具體的實現類都可以選擇以它自己的方式對元素進行儲存和排序。有的集合類允許重複的鍵,有些不允許。
克隆(cloning)或者是序列化(serialization)的語義和含義是跟具體的實現相關的。因此,應該由集合類的具體實現來決定如何被克隆或者是序列化。
3.什麼是迭代器(Iterator)?
Iterator介面提供了很多對集合元素進行迭代的方法。每一個集合類都包含了可以返回迭代器例項的迭代方法。迭代器可以在迭代的過程中刪除底層集合的元素,安全。
4.Iterator和ListIterator的區別是什麼?
=》Iterator可用來遍歷Set和List集合,但是ListIterator只能用來遍歷List。
=》Iterator對集合只能是前向遍歷,ListIterator既可以前向也可以後向。
=》ListIterator實現了Iterator介面,幷包含其他的功能,比如:增加元素,替換元素,獲取前一個和後一個元素的索引,等等。
5.快速失敗(fail-fast)和安全失敗(fail-safe)的區別是什麼?
Iterator的安全失敗是基於對底層集合做拷貝,因此,它不受源集合上修改的影響。java.util包下面的所有的集合類都是快速失敗的,而java.util.concurrent包下面的所有的類都是安全失敗的。快速失敗的迭代器會丟擲ConcurrentModificationException異常,而安全失敗的迭代器永遠不會丟擲這樣的異常。
6.Java中的HashMap的工作原理是什麼?
Java中的HashMap是以鍵值對(key-value)的形式儲存元素的。HashMap需要一個hash函式,它使用hashCode()和equals()方法來向集合/從集合新增和檢索元素。
當呼叫put()方法的時候,HashMap會計算key的hash值,然後把鍵值對儲存在集合中合適的索引上。如果key已經存在了,value會被更新成新值。HashMap的一些重要的特性是它的容量(capacity),負載因子(load factor)和擴容極限(threshold resizing)。
擴容牽扯到rehash的過程:增加1倍,然後重新計算hash值並且搬運元素到新的雜湊表當中。
get()方法,同樣是……
7. hashCode()和equals()方法的重要性體現在什麼地方?
Java中的HashMap使用hashCode()和equals()方法來確定鍵值對的索引,當根據鍵獲取值的時候也會用到這兩個方法。如果沒有正確的實現這兩個方法,兩個不同的鍵可能會有相同的hash值,因此,可能會被集合認為是相等的。而且,這兩個方法也用來發現重複元素。所以這兩個方法的實現對HashMap的精確性和正確性是至關重要的。
8.HashMap和Hashtable有什麼區別?
HashMap和Hashtable都實現了Map介面,很多特性相似。但有不同點:
HashMap允許鍵和值是null,而Hashtable不允許鍵或者值是null。
Hashtable是同步的,而HashMap不是。因此,HashMap更適合於單執行緒環境,而Hashtable適合於多執行緒環境。
HashMap提供了可供應用迭代的鍵的集合keySet(),因此,HashMap是快速失敗fast-fail的。
另一方面,Hashtable提供了對鍵的列舉(Enumeration)。一般認為Hashtable是一個遺留的類。
9.陣列(Array)和列表(ArrayList)有什麼區別?什麼時候應該使用Array而不是ArrayList?
不同點:
定義上:Array可以包含基本型別和物件型別,ArrayList只能包含物件型別。
容量上:Array大小固定,ArrayList的大小是動態變化的。
操作上:ArrayList提供更多的方法和特性,如:addAll(),removeAll(),iterator()等等。
使用基本資料型別或者知道資料元素數量的時候可以考慮Array;
ArrayList處理固定數量的基本型別資料型別時會自動裝箱來減少編碼工作量,但是相對較慢。
10.ArrayList和LinkedList有什麼區別?
兩者都實現了List介面,他們有以下不同點:
資料結構上:
ArrayList是基於索引的陣列形式,可隨機訪問元素, 時間複雜度O(1);
LinkedList是元素列表的形式儲存它的資料,每一個元素都和它的前一個和後一個元素連結在一起,在這種情況下,查詢某個元素的時間複雜度是O(n)。
操作上:
ArrayList新增,刪除操作比較慢,重新計算大小或者是更新索引。
LinkedList的插入,新增,刪除操作速度更快,不需要更新索引。
記憶體上:
LinkedList比ArrayList更佔記憶體,因為LinkedList為每一個節點儲存了兩個引用,一個指向前一個元素,一個指向下一個元素。
11.Comparable和Comparator介面是幹什麼的?列出它們的區別。
Java提供了只包含一個compareTo()方法的Comparable介面。這個方法可以個給兩個物件排序。具體來說,它返回負數,0,正數來表明輸入物件小於,等於,大於已經存在的物件。
Java提供了包含compare()和equals()兩個方法的Comparator介面。compare()方法用來給兩個輸入引數排序,返回負數,0,正數表明第一個引數是小於,等於,大於第二個引數。equals()方法需要一個物件作為引數,它用來決定輸入引數是否和comparator相等。只有當輸入引數也是一個comparator並且輸入引數和當前comparator的排序結果是相同的時候,這個方法才返回true。
12.什麼是Java優先順序佇列(Priority Queue)?
PriorityQueue是一個基於優先順序堆的無界有序佇列,它的元素是按照自然順序(natural order)排序的。在建立的時候,我們可以給它提供一個負責給元素排序的比較器。PriorityQueue不允許null值,因為他們沒有自然順序,或者說他們沒有任何的相關聯的比較器。最後,PriorityQueue不是執行緒安全的,入隊和出隊的時間複雜度是O(log(n))。
13.你瞭解大O符號(big-O notation)麼?你能給出不同資料結構的例子麼?
大O:描述了當資料結構裡面的元素增加的時候,演算法的規模或者是效能在最壞的場景下有多麼好。
大O符號也可用來描述其他的行為,比如:記憶體消耗。因為集合類實際上是資料結構,我們一般使用大O符號基於時間,記憶體和效能來選擇最好的實現。大O符號可以對大量資料的效能給出一個很好的說明。
14.如何權衡是使用無序的陣列還是有序的陣列?
有序陣列最大的好處在於查詢的時間複雜度是O(log n),而無序陣列是O(n)。有序陣列的缺點是插入操作的時間複雜度是O(n),因為值大的元素需要往後移動來給新元素騰位置。相反,無序陣列的插入時間複雜度是常量O(1)。
所以,查詢操作多的時候,使用有序;增刪操作多的使用無序的即可。
15.Java集合類框架的最佳實踐有哪些?
根據應用的需要正確選擇要使用的集合的型別對效能非常重要,比如:假如元素的大小是固定的,而且能事先知道,我們就應該用Array而不是ArrayList。
有些集合類允許指定初始容量。因此,如果我們能估計出儲存的元素的數目,我們可以設定初始容量來避免重新計算hash值或者是擴容。
為了型別安全,可讀性和健壯性的原因總是要使用泛型。同時,使用泛型還可以避免執行時的ClassCastException。
使用JDK提供的不變類(immutable class)作為Map的鍵可以避免為我們自己的類實現hashCode()和equals()方法。
程式設計的時候介面優於實現。
底層的集合實際上是空的情況下,返回長度是0的集合或者是陣列,不要返回null。
16.Enumeration介面和Iterator介面的區別有哪些?
Enumeration速度是Iterator的2倍,同時佔用更少的記憶體。
但是,Iterator遠遠比Enumeration安全,因為其他執行緒不能夠修改正在被iterator遍歷的集合裡面的物件。同時,Iterator允許呼叫者刪除底層集合裡面的元素,這對Enumeration來說是不可能的。
17.HashSet和TreeSet有什麼區別?
HashSet是由一個雜湊表來實現的,元素無,add(),remove(),contains()方法的時間複雜度是O(1)。
另一方面,TreeSet是由一個樹形結構(平衡二叉排序樹)來實現的,它裡面的元素是有序的。因此,add(),remove(),contains()方法的時間複雜度是O(logn)。
5.1集合框架基礎
1.Java集合框架是什麼?說出一些集合框架的優點?
每種程式語言中都有集合,最初的Java版本包含幾種集合類:Vector、Stack、HashTable和Array。隨著集合的廣泛使用,Java1.2提出了囊括所有集合介面、實現和演算法的集合框架。在保證執行緒安全的情況下使用泛型和併發集合類,Java已經經歷了很久。它還包括在Java併發包中,阻塞介面以及它們的實現。集合框架的部分優點如下:
(1)使用核心集合類降低開發成本,而非實現我們自己的集合類。
(2)隨著使用經過嚴格測試的集合框架類,程式碼質量會得到提高。
(3)通過使用JDK附帶的集合類,可以降低程式碼維護成本。
(4)複用性和可操作性。
2.集合框架中的泛型有什麼優點?
Java1.5引入了泛型,所有的集合介面和實現都大量地使用它。
泛型允許我們為集合提供一個可以容納的物件型別,因此,如果你新增其它型別的任何元素,它會在編譯時報錯。這避免了在執行時出現ClassCastException,因為你將會在編譯時得到報錯資訊。泛型也使得程式碼整潔,我們不需要使用顯式轉換和instanceOf操作符。
它也給執行時帶來好處,因為不會產生型別檢查的位元組碼指令。
3.Java集合框架的基礎介面有哪些?
Collection為集合層級的根介面。一個集合代表一組物件,這些物件即為它的元素。Java平臺不提供這個介面任何直接的實現。
Set是一個不能包含重複元素的集合。這個介面對數學集合抽象進行建模,被用來代表集合,就如一副牌。
List是一個有序集合,可以包含重複元素。你可以通過它的索引來訪問任何元素。List更像長度動態變換的陣列。
Map是一個將key對映到value的物件.一個Map不能包含重複的key:每個key最多隻能對映一個value。
一些其它的介面有Queue、Dequeue、SortedSet、SortedMap和ListIterator。
4.為何Collection不從Cloneable和Serializable介面繼承?
Collection介面指定一組物件,物件即為它的元素。如何維護這些元素由Collection的具體實現決定。例如,一些如List的Collection實現允許重複的元素,而其它的如Set就不允許。很多Collection實現有一個公有的clone方法。然而,把它放到集合的所有實現中也是沒有意義的。這是
因為Collection是一個抽象表現,而重要的是實現。
當與具體實現打交道的時候,克隆或序列化的語義和含義才發揮作用。所以,具體實現應該決定如何對它進行克隆或序列化,或它是否可以被克隆或序列化。
在所有的實現中授權克隆和序列化,最終導致更少的靈活性和更多的限制。特定的實現應該決定它是否可以被克隆和序列化。
5.為何Map介面不繼承Collection介面?
儘管Map介面和它的實現也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map繼承Collection無論誰繼承誰都毫無意義。
如果Map繼承Collection介面,那麼元素去哪兒?Map包含key-value對,它提供抽取key或value列表集合的方法,但是它不適合“一組物件”規範。
5.2 Iterator
6.Iterator是什麼?
Iterator介面提供遍歷任何Collection的介面。我們可以從一個Collection中使用迭代器方法來獲取迭代器例項。迭代器取代了Java集合框架中的Enumeration。迭代器允許呼叫者在迭代過程中移除元素。
7.Enumeration和Iterator介面的區別?
Enumeration的速度是Iterator的兩倍,使用更少的記憶體。Enumeration是非常基礎的,也滿足了基礎的需要。但是,Iterator更加安全,因為當一個集合正在被遍歷的時候,它會阻止其它執行緒去修改集合。
迭代器取代了Java集合框架中的Enumeration,並允許呼叫者從集合中移除元素,而Enumeration不能做到。為了使它的功能更加清晰,迭代器方法名已經經過改善。
8.為何沒有像Iterator.add()這樣的方法,向集合中新增元素?
語義不明,已知的是,Iterator的協議不能確保迭代的次序。然而要注意,ListIterator沒有提供一個add操作,它要確保迭代的順序。
9.為何迭代器沒有一個方法可以直接獲取下一個元素,而不需要移動遊標?
它可以在當前Iterator的頂層實現,但是它用得很少,如果將它加到介面中,每個繼承都要去實現它,這沒有意義。
10.Iterater和ListIterator之間有什麼區別?
(1)我們可以使用Iterator來遍歷Set和List集合,而ListIterator只能遍歷List。
(2)Iterator只可以向前遍歷,而ListIterator可以雙向遍歷。
(3)ListIterator從Iterator介面繼承,然後添加了一些額外的功能,比如新增一個元素、替換一個元素、獲取前面或後面元素的索引位置。
11.遍歷一個List有哪些不同的方式?
使用迭代器更加執行緒安全,因為它可以確保,在當前遍歷的集合元素被更改的時候,它會丟擲ConcurrentModificationException。List<String> strList = new ArrayList<>(); //使用for-each迴圈 for(String obj : strList){ System.out.println(obj); } //using iterator Iterator<String> it = strList.iterator(); while(it.hasNext()){ String obj = it.next(); System.out.println(obj); }
12.通過迭代器fail-fast屬性,你明白了什麼?
每次嘗試獲取下一個元素時,Iterator fail-fast屬性檢查當前集合結構裡的任何改動。如有改動,則丟擲異常ConcurrentModificationException。Collection中所有Iterator的實現都是按fail-fast來設計的(ConcurrentHashMap和CopyOnWriteArrayList這類併發集合類除外)。
13.fail-fast與fail-safe有什麼區別?
(1)Java.util包中的所有集合類都被設計為fail-fast的,而java.util.concurrent中的集合類都為fail-safe的。
(2)fail-fast檢測集合結構改變的原理,Iterator直接訪問集合的資料結構,它保留一個標誌”mods”,在Iterator每次呼叫hasNext()或者是next()方法時,首先檢測”mods”狀態,如果結構已經改變,則丟擲異常。
fail-safe Iterator的實現原理是,先將原集合拷貝一份,在拷貝上開展遍歷,因此不會引起ConcurrentModification異常。因此,Fail Safe Iterator存在兩個缺陷: 額外的空間開銷 和遍歷資料不一定是最新的。
14.在迭代一個集合的時候,如何避免ConcurrentModificationException?
在遍歷一個集合的時候,我們可以使用併發集合類來避免ConcurrentModificationException,比如使用CopyOnWriteArrayList,而不是ArrayList。
即使用java.uitl.concurrenet中的集合類代替java.util包下的集合類。
15.為何Iterator介面沒有具體的實現?
Iterator介面定義了遍歷集合的方法,但它的實現則是集合實現類的責任。每個能夠返回用於遍歷的Iterator的集合類都有它自己的Iterator實現內部類。
這就允許集合類去選擇迭代器是fail-fast還是fail-safe的。比如,ArrayList迭代器是fail-fast的,而CopyOnWriteArrayList迭代器是fail-safe的。
16.UnsupportedOperationException是什麼?
UnsupportedOperationException是用於表明操作不支援的異常。在JDK類中已被大量運用,在集合框架java.util.Collections.UnmodifiableCollection將會在所有add和remove操作中丟擲這個異常。
5.3 Map/List/Set/Queue/Stack
17.在Java中,HashMap是如何工作的?
HashMap在Map.Entry靜態內部類實現中儲存key-value鍵值對。使用“陣列和連結串列”的儲存結構,總體使用“鏈地址法”來解決雜湊衝突。
HashMap使用雜湊演算法,在put和get方法中,它都使用了hashCode()和equals()方法。
put()方法:首先,HashMap使用Key hashCode()和雜湊演算法來找出儲存key-value對的索引。Entry儲存在LinkedList中,所以如果存在entry,它使用equals()方法來檢查傳遞的key是否已經存在,如果存在,它會覆蓋value,如果不存在,它會建立一個新的entry然後儲存。
get()方法:當我們通過傳遞key呼叫get方法時,它再次使用hashCode()來找到陣列中的索引,然後使用equals()方法找出正確的Entry,然後返回它的值。
其它關於HashMap比較重要的問題是容量、負荷係數和閥值調整。HashMap預設的初始容量是32,負荷係數是0.75。閥值是為負荷係數乘以容量,無論何時我們嘗試新增一個entry,如果map的大小比閥值大的時候,HashMap會對map的內容進行重新雜湊Rehash,且使用更大的容量。容量總是2的冪,所以如果你知道你需要儲存大量的key-value對,比如快取從資料庫裡面拉取的資料,使用正確的容量和負荷係數對HashMap進行初始化是個不錯的做法。
Rehash演算法:如果雜湊地址不夠,要對hash表進行擴容,擴容為原來的2倍,然後將原來hash表中的所有計算好hash地址的元素重新計算hashCode,並且搬到擴容後的hash表後的LinkedList連結串列中。
18.hashCode()和equals()方法有何重要性?
HashMap使用Key物件的hashCode()和equals()方法去決定key-value對的索引。當我們試著從HashMap中獲取值的時候,這些方法也會被用到。如果這些方法沒有被正確地實現,在這種情況下,兩個不同Key也許會產生相同的hashCode()和equals()輸出,HashMap將會認為它們是相同的,然後覆蓋它們,而非把它們儲存到不同的地方。同樣的,所有不允許儲存重複資料的集合類都使用hashCode()和equals()去查詢重複,所以正確實現它們非常重要。equals()和hashCode()的實現應該遵循以下規則:
(1)如果o1.equals(o2),那麼o1.hashCode() == o2.hashCode()總是為true的。
(2)如果o1.hashCode() == o2.hashCode(),並不意味著o1.equals(o2)會為true。
19.我們能否使用任何類作為Map的key?
我們可以使用任何類作為Map的key,然而在使用它們之前,需要考慮以下幾點:
(1)如果類重寫了equals()方法,它也應該重寫hashCode()方法。
(2)類的所有例項需要遵循與equals()和hashCode()相關的規則。(請參考之前提到的這些規則)
(3)如果一個類沒有使用equals(),你不應該在hashCode()中使用它。
(4)使用者自定義key類的最佳實踐是使之為不可變的,這樣,hashCode()值可以被快取起來,擁有更好的效能。不可變的類也可以確保hashCode()和equals()在未來不會改變,這樣就會解決與可變相關的問題了。
比如,我有一個類MyKey,在HashMap中使用它。
那就是為何String和Integer這些不可變類被作為HashMap的key大量使用(原因就是防止可變類的修改導致再次利用key查詢索引的時候不可復現原來的索引,即查詢索引失敗)。//傳遞給MyKey的name引數被用於equals()和hashCode()中 MyKey key = new MyKey('Pankaj'); //assume hashCode=1234 myHashMap.put(key, 'Value'); // 以下的程式碼會改變key的hashCode()和equals()值 key.setName('Amit'); //assume new hashCode=7890 //下面會返回null,因為HashMap會嘗試查詢儲存同樣索引的key,而key已被改變了,匹配失敗,返回null myHashMap.get(new MyKey('Pankaj'));
20.Map介面提供了哪些不同的集合檢視?
Map介面提供三個集合檢視:
(1)Set keyset():返回map中包含的所有key的一個Set檢視。集合是受map支援的,map的變化會在集合中反映出來,反之亦然。當一個迭代器正在遍歷一個集合時,若map被修改了(除迭代器自身的移除操作以外),迭代器的結果會變為未定義。集合支援通過Iterator的Remove、Set.remove、removeAll、retainAll和clear操作進行元素移除,從map中移除對應的對映。它不支援add和addAll操作。
(2)Collectionvalues():返回一個map中包含的所有value的一個Collection檢視。這個collection受map支援的,map的變化會在collection中反映出來,反之亦然。當一個迭代器正在遍歷一個collection時,若map被修改了(除迭代器自身的移除操作以外),迭代器的結果會變為未定義。集合支援通過Iterator的Remove、Set.remove、removeAll、retainAll和clear操作進行元素移除,從map中移除對應的對映。它不支援add和addAll操作。
(3)Set<Map.Entry<K,V>>entrySet():返回一個map鍾包含的所有對映的一個集合檢視。這個集合受map支援的,map的變化會在collection中反映出來,反之亦然。當一個迭代器正在遍歷一個集合時,若map被修改了(除迭代器自身的移除操作,以及對迭代器返回的entry進行setValue外),迭代器的結果會變為未定義。集合支援通過Iterator的Remove、Set.remove、removeAl