50道Java集合经典面试题
1. Arraylist與LinkedList區(qū)別
可以從它們的底層數(shù)據(jù)結(jié)構(gòu)、效率、開銷進(jìn)行闡述哈
ArrayList是數(shù)組的數(shù)據(jù)結(jié)構(gòu),LinkedList是鏈表的數(shù)據(jù)結(jié)構(gòu)。
隨機訪問的時候,ArrayList的效率比較高,因為LinkedList要移動指針,而ArrayList是基于索引(index)的數(shù)據(jù)結(jié)構(gòu),可以直接映射到。
插入、刪除數(shù)據(jù)時,LinkedList的效率比較高,因為ArrayList要移動數(shù)據(jù)。
LinkedList比ArrayList開銷更大,因為LinkedList的節(jié)點除了存儲數(shù)據(jù),還需要存儲引用。
2. Collections.sort和Arrays.sort的實現(xiàn)原理
Collection.sort是對list進(jìn)行排序,Arrays.sort是對數(shù)組進(jìn)行排序。
Collections.sort底層實現(xiàn)
Collections.sort方法調(diào)用了list.sort方法
list.sort方法調(diào)用了Arrays.sort的方法
因此,Collections.sort方法底層就是調(diào)用的Array.sort方法
Arrays.sort底層實現(xiàn)
Arrays的sort方法,如下:
如果比較器為null,進(jìn)入sort(a)方法。如下:
因此,Arrays的sort方法底層就是:
legacyMergeSort(a),歸并排序,
ComparableTimSort.sort():即Timsort排序。
Timesort排序
Timsort排序是結(jié)合了合并排序(merge.sort)和插入排序(insertion sort)而得出的排序方法;
1.當(dāng)數(shù)組長度小于某個值,采用的是二分插入排序算法,如下:
找到各個run,并入棧。
按規(guī)則合并run。
3. HashMap原理,java8做了什么改變
HashMap是以鍵值對存儲數(shù)據(jù)的集合容器
HashMap是非線性安全的。
HashMap底層數(shù)據(jù)結(jié)構(gòu):數(shù)組+(鏈表、紅黑樹),jdk8之前是用數(shù)組+鏈表的方式實現(xiàn),jdk8引進(jìn)了紅黑樹
Hashmap數(shù)組的默認(rèn)初始長度是16,key和value都允許null的存在
HashMap的內(nèi)部實現(xiàn)數(shù)組是Node[]數(shù)組,上面存放的是key-value鍵值對的節(jié)點。HashMap通過put和get方法存儲和獲取。
HashMap的put方法,首先計算key的hashcode值,定位到對應(yīng)的數(shù)組索引,然后再在該索引的單向鏈表上進(jìn)行循環(huán)遍歷,用equals比較key是否存在,如果存在則用新的value覆蓋原值,如果沒有則向后追加。
jdk8中put方法:先判斷Hashmap是否為空,為空就擴(kuò)容,不為空計算出key的hash值i,然后看table[i]是否為空,為空就直接插入,不為空判斷當(dāng)前位置的key和table[i]是否相同,相同就覆蓋,不相同就查看table[i]是否是紅黑樹節(jié)點,如果是的話就用紅黑樹直接插入鍵值對,如果不是開始遍歷鏈表插入,如果遇到重復(fù)值就覆蓋,否則直接插入,如果鏈表長度大于8,轉(zhuǎn)為紅黑樹結(jié)構(gòu),執(zhí)行完成后看size是否大于閾值threshold,大于就擴(kuò)容,否則直接結(jié)束。
Hashmap解決hash沖突,使用的是鏈地址法,即數(shù)組+鏈表的形式來解決。put執(zhí)行首先判斷table[i]位置,如果為空就直接插入,不為空判斷和當(dāng)前值是否相等,相等就覆蓋,如果不相等的話,判斷是否是紅黑樹節(jié)點,如果不是,就從table[i]位置開始遍歷鏈表,相等覆蓋,不相等插入。
HashMap的get方法就是計算出要獲取元素的hash值,去對應(yīng)位置獲取即可。
HashMap的擴(kuò)容機制,Hashmap的擴(kuò)容中主要進(jìn)行兩步,第一步把數(shù)組長度變?yōu)樵瓉淼膬杀?#xff0c;第二部把舊數(shù)組的元素重新計算hash插入到新數(shù)組中,jdk8時,不用重新計算hash,只用看看原來的hash值新增的一位是零還是1,如果是1這個元素在新數(shù)組中的位置,是原數(shù)組的位置加原數(shù)組長度,如果是零就插入到原數(shù)組中。擴(kuò)容過程第二部一個非常重要的方法是transfer方法,采用頭插法,把舊數(shù)組的元素插入到新數(shù)組中。
HashMap大小為什么是2的冪次方?效率高+空間分布均勻
有關(guān)于HashMap這些常量設(shè)計目的,也可以看我這篇文章:面試加分項-HashMap源碼中這些常量的設(shè)計目的
4. List 和 Set,Map 的區(qū)別
List 以索引來存取元素,有序的,元素是允許重復(fù)的,可以插入多個null。
Set 不能存放重復(fù)元素,無序的,只允許一個null
Map 保存鍵值對映射,映射關(guān)系可以一對一、多對一
List 有基于數(shù)組、鏈表實現(xiàn)兩種方式
Set、Map 容器有基于哈希存儲和紅黑樹兩種方式實現(xiàn)
Set 基于 Map 實現(xiàn),Set 里的元素值就是 Map的鍵值
5. poll()方法和 remove()方法的區(qū)別?
Queue隊列中,poll() 和 remove() 都是從隊列中取出一個元素,在隊列元素為空的情況下,remove() 方法會拋出異常,poll() 方法只會返回 null 。
看一下源碼的解釋吧:
/** * Retrieves and removes the head of this queue. This method differs * from {@link #poll poll} only in that it throws an exception if this * queue is empty. * * @return the head of this queue * @throws NoSuchElementException if this queue is empty */????E?remove(); /** * Retrieves and removes the head of this queue, * or returns {@code null} if this queue is empty. * * @return the head of this queue, or {@code null} if this queue is empty */E poll();6. HashMap,HashTable,ConcurrentHash的共同點和區(qū)別
HashMap
底層由鏈表+數(shù)組+紅黑樹實現(xiàn)
可以存儲null鍵和null值
線性不安全
初始容量為16,擴(kuò)容每次都是2的n次冪
加載因子為0.75,當(dāng)Map中元素總數(shù)超過Entry數(shù)組的0.75,觸發(fā)擴(kuò)容操作.
并發(fā)情況下,HashMap進(jìn)行put操作會引起死循環(huán),導(dǎo)致CPU利用率接近100%
HashMap是對Map接口的實現(xiàn)
HashTable
HashTable的底層也是由鏈表+數(shù)組+紅黑樹實現(xiàn)。
無論key還是value都不能為null
它是線性安全的,使用了synchronized關(guān)鍵字。
HashTable實現(xiàn)了Map接口和Dictionary抽象類
Hashtable初始容量為11
ConcurrentHashMap
ConcurrentHashMap的底層是數(shù)組+鏈表+紅黑樹
不能存儲null鍵和值
ConcurrentHashMap是線程安全的
ConcurrentHashMap使用鎖分段技術(shù)確保線性安全
JDK8為何又放棄分段鎖,是因為多個分段鎖浪費內(nèi)存空間,競爭同一個鎖的概率非常小,分段鎖反而會造成效率低。
7. 寫一段代碼在遍歷 ArrayList 時移除一個元素
因為foreach刪除會導(dǎo)致快速失敗問題,fori順序遍歷會導(dǎo)致重復(fù)元素沒刪除,所以正確解法如下:
第一種遍歷,倒敘遍歷刪除
for(int i=list.size()-1; i>-1; i--){ if(list.get(i).equals("jay")){ list.remove(list.get(i)); }}第二種,迭代器刪除
Iterator itr = list.iterator();while(itr.hasNext()) { if(itr.next().equals("jay") { itr.remove(); }}8. Java中怎么打印數(shù)組?
數(shù)組是不能直接打印的哈,如下:
public class Test {public static void main(String[] args) { String[] jayArray = {"jay", "boy"}; System.out.println(jayArray); }}//output[Ljava.lang.String;@1540e19d打印數(shù)組可以用流的方式Strem.of().foreach(),如下:
public class Test {public static void main(String[] args) { String[] jayArray = {"jay", "boy"}; Stream.of(jayArray).forEach(System.out::println); }}//outputjayboy打印數(shù)組,最優(yōu)雅的方式可以用這個APi,Arrays.toString()
public class Test { public static void main(String[] args) { String[] jayArray = {"jay", "boy"}; System.out.println(Arrays.toString(jayArray)); }}//output[jay, boy]9. TreeMap底層?
TreeMap實現(xiàn)了SotredMap接口,它是有序的集合。
TreeMap底層數(shù)據(jù)結(jié)構(gòu)是一個紅黑樹,每個key-value都作為一個紅黑樹的節(jié)點。
如果在調(diào)用TreeMap的構(gòu)造函數(shù)時沒有指定比較器,則根據(jù)key執(zhí)行自然排序。
10. HashMap 的擴(kuò)容過程
Hashmap的擴(kuò)容:
第一步把數(shù)組長度變?yōu)樵瓉淼膬杀?#xff0c;
第二步把舊數(shù)組的元素重新計算hash插入到新數(shù)組中。
jdk8時,不用重新計算hash,只用看看原來的hash值新增的一位是零還是1,如果是1這個元素在新數(shù)組中的位置,是原數(shù)組的位置加原數(shù)組長度,如果是零就插入到原數(shù)組中。擴(kuò)容過程第二步一個非常重要的方法是transfer方法,采用頭插法,把舊數(shù)組的元素插入到新數(shù)組中。
11. HashSet是如何保證不重復(fù)的
可以看一下HashSet的add方法,元素E作為HashMap的key,我們都知道HashMap的可以是不允許重復(fù)的,哈哈。
public boolean add(E e) { return map.put(e, PRESENT)==null;}12. HashMap 是線程安全的嗎,為什么不是線程安全的?死循環(huán)問題?
不是線性安全的。
并發(fā)的情況下,擴(kuò)容可能導(dǎo)致死循環(huán)問題。
13. LinkedHashMap的應(yīng)用,底層,原理
LinkedHashMap維護(hù)著一個運行于所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序(insert-order)或者是訪問順序,其中默認(rèn)的迭代訪問順序就是插入順序,即可以按插入的順序遍歷元素,這點和HashMap有很大的不同。
LRU算法可以用LinkedHashMap實現(xiàn)。
14. 哪些集合類是線程安全的?哪些不安全?
線性安全的
Vector:比Arraylist多了個同步化機制。
Hashtable:比Hashmap多了個線程安全。
ConcurrentHashMap:是一種高效但是線程安全的集合。
Stack:棧,也是線程安全的,繼承于Vector。
線性不安全的
Hashmap
Arraylist
LinkedList
HashSet
TreeSet
TreeMap
15. ArrayList 和 Vector 的區(qū)別是什么?
Vector是線程安全的,ArrayList不是線程安全的。
ArrayList在底層數(shù)組不夠用時在原來的基礎(chǔ)上擴(kuò)展0.5倍,Vector是擴(kuò)展1倍。
Vector只要是關(guān)鍵性的操作,方法前面都加了synchronized關(guān)鍵字,來保證線程的安全性。
16. Collection與Collections的區(qū)別是什么?
Collection是Java集合框架中的基本接口,如List接口也是繼承于它
Collections是Java集合框架提供的一個工具類,其中包含了大量用于操作或返回集合的靜態(tài)方法。如下:
17. 如何決定使用 HashMap 還是TreeMap?
這個點,主要考察HashMap和TreeMap的區(qū)別。
TreeMap實現(xiàn)SortMap接口,能夠把它保存的記錄根據(jù)鍵排序,默認(rèn)是按key的升序排序,也可以指定排序的比較器。當(dāng)用Iterator遍歷TreeMap時,得到的記錄是排過序的。
18. 如何實現(xiàn)數(shù)組和 List之間的轉(zhuǎn)換?
List 轉(zhuǎn) Array
List 轉(zhuǎn)Array,必須使用集合的 toArray(T[] array),如下:
List<String> list = new ArrayList<String>();list.add("jay");list.add("tianluo"); // 使用泛型,無需顯式類型轉(zhuǎn)換String[] array = list.toArray(new String[list.size()]);System.out.println(array[0]);如果直接使用 toArray 無參方法,返回值只能是 Object[] 類,強轉(zhuǎn)其他類型可能有問題,demo如下:
List<String> list = new ArrayList<String>();list.add("jay");list.add("tianluo"); String[] array = (String[]) list.toArray();System.out.println(array[0]);運行結(jié)果:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String; at Test.main(Test.java:14)Array 轉(zhuǎn)List
使用Arrays.asList() 把數(shù)組轉(zhuǎn)換成集合時,不能使用修改集合相關(guān)的方法啦,如下:
String[] str = new String[] { "jay", "tianluo" };List list = Arrays.asList(str);list.add("boy");運行結(jié)果如下:
Exception in thread "main" java.lang.UnsupportedOperationException at java.util.AbstractList.add(AbstractList.java:148) at java.util.AbstractList.add(AbstractList.java:108) at Test.main(Test.java:13)因為 Arrays.asList不是返回java.util.ArrayList,而是一個內(nèi)部類ArrayList。
可以這樣使用彌補這個缺點:
//方式一:ArrayList< String> arrayList = new ArrayList<String>(strArray.length);Collections.addAll(arrayList, strArray);//方式二:ArrayList<String> list = new ArrayList<String>(Arrays.asList(strArray)) ;19. 迭代器 Iterator 是什么?怎么用,有什么特點?
public interface Collection<E> extends Iterable<E> { Iterator<E> iterator();方法如下:
next() 方法獲得集合中的下一個元素hasNext() 檢查集合中是否還有元素remove() 方法將迭代器新返回的元素刪除forEachRemaining(Consumer<? super E> action) 方法,遍歷所有元素Iterator 主要是用來遍歷集合用的,它的特點是更加安全,因為它可以確保,在當(dāng)前遍歷的集合元素被更改的時候,就會拋出 ConcurrentModificationException 異常。
使用demo如下:
List<String> list = new ArrayList<>();Iterator<String> it = list. iterator();while(it. hasNext()){ String obj = it. next(); System. out. println(obj);}20. Iterator 和 ListIterator 有什么區(qū)別?
ListIterator 比 Iterator有更多的方法。
ListIterator只能用于遍歷List及其子類,Iterator可用來遍歷所有集合,
ListIterator遍歷可以是逆向的,因為有previous()和hasPrevious()方法,而Iterator不可以。
ListIterator有add()方法,可以向List添加對象,而Iterator卻不能。
ListIterator可以定位當(dāng)前的索引位置,因為有nextIndex()和previousIndex()方法,而Iterator不可以。
ListIterator可以實現(xiàn)對象的修改,set()方法可以實現(xiàn)。Iierator僅能遍歷,不能修改哦。
21. 怎么確保一個集合不能被修改?
很多朋友很可能想到用final關(guān)鍵字進(jìn)行修飾,final修飾的這個成員變量,如果是基本數(shù)據(jù)類型,表示這個變量的值是不可改變的,如果是引用類型,則表示這個引用的地址值是不能改變的,但是這個引用所指向的對象里面的內(nèi)容還是可以改變滴~驗證一下,如下:
public class Test { //final 修飾 private static final Map<Integer, String> map = new HashMap<Integer, String>(); { map.put(1, "jay"); map.put(2, "tianluo"); }public static void main(String[] args) { map.put(1, "boy"); System.out.println(map.get(1)); }}運行結(jié)果如下:
//可以洗發(fā)現(xiàn),final修飾,集合還是會被修改呢boy嘻嘻,那么,到底怎么確保一個集合不能被修改呢,看以下這三哥們~
unmodifiableMap
unmodifiableList
unmodifiableSet
再看一下demo吧
public class Test {private static Map<Integer, String> map = new HashMap<Integer, String>(); { map.put(1, "jay"); map.put(2, "tianluo");}public static void main(String[] args) { map = Collections.unmodifiableMap(map); map.put(1, "boy"); System.out.println(map.get(1)); }}運行結(jié)果:
// 可以發(fā)現(xiàn),unmodifiableMap確保集合不能修改啦,拋異常了Exception in thread "main" java.lang.UnsupportedOperationException at java.util.Collections$UnmodifiableMap.put(Collections.java:1457) at Test.main(Test.java:14)22. 快速失敗(fail-fast)和安全失敗(fail-safe)的區(qū)別是什么?
快速失敗
在用迭代器遍歷一個集合對象時,如果遍歷過程中對集合對象的內(nèi)容進(jìn)行了修改(增加、刪除、修改),則會拋出Concurrent Modification Exception。
public class Test {public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(1); list.add(2);Iterator iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); list.add(3); System.out.println(list.size()); }}}運行結(jié)果:
1Exception in thread "main" java.util.ConcurrentModificationException3 at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at Test.main(Test.java:12)安全失敗
采用安全失敗機制的集合容器,在遍歷時不是直接在集合內(nèi)容上訪問的,而是先復(fù)制原有集合內(nèi)容,在拷貝的集合上進(jìn)行遍歷。
public class Test {public static void main(String[] args) { List<Integer> list = new CopyOnWriteArrayList<>(); list.add(1); list.add(2);Iterator iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); list.add(3); System.out.println("list size:"+list.size()); }}}運行結(jié)果:
1list size:32list size:4其實,在java.util.concurrent 并發(fā)包的集合,如 ConcurrentHashMap, CopyOnWriteArrayList等,默認(rèn)為都是安全失敗的。
23. 什么是Java優(yōu)先級隊列(Priority Queue)?
優(yōu)先隊列PriorityQueue是Queue接口的實現(xiàn),可以對其中元素進(jìn)行排序
優(yōu)先隊列中元素默認(rèn)排列順序是升序排列
但對于自己定義的類來說,需要自己定義比較器
方法:
peek()//返回隊首元素poll()//返回隊首元素,隊首元素出隊列add()//添加元素size()//返回隊列元素個數(shù)isEmpty()//判斷隊列是否為空,為空返回true,不空返回false特點:
1.基于優(yōu)先級堆
2.不允許null值
3.線程不安全
4.出入隊時間復(fù)雜度O(log(n))
5.調(diào)用remove()返回堆內(nèi)最小值
24. JAVA8的ConcurrentHashMap為什么放棄了分段鎖,有什么問題嗎,如果你來設(shè)計,你如何設(shè)計。
jdk8 放棄了分段鎖而是用了Node鎖,減低鎖的粒度,提高性能,并使用CAS操作來確保Node的一些操作的原子性,取代了鎖。
可以跟面試官聊聊悲觀鎖和CAS樂觀鎖的區(qū)別,優(yōu)缺點哈~
25. 阻塞隊列的實現(xiàn),ArrayBlockingQueue的底層實現(xiàn)?
ArrayBlockingQueue是數(shù)組實現(xiàn)的線程安全的有界的阻塞隊列,繼承自AbstractBlockingQueue,間接的實現(xiàn)了Queue接口和Collection接口。底層以數(shù)組的形式保存數(shù)據(jù)(實際上可看作一個循環(huán)數(shù)組)。常用的操作包括 add ,offer,put,remove,poll,take,peek。
可以結(jié)合線程池跟面試官講一下哦~
26. Java 中的 LinkedList是單向鏈表還是雙向鏈表?
哈哈,看源碼吧,是雙向鏈表
private static class Node<E> { E item; Node<E> next; Node<E> prev;Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }27. 說一說ArrayList 的擴(kuò)容機制吧
ArrayList擴(kuò)容的本質(zhì)就是計算出新的擴(kuò)容數(shù)組的size后實例化,并將原有數(shù)組內(nèi)容復(fù)制到新數(shù)組中去。
public boolean add(E e) { //擴(kuò)容 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));} private static int calculateCapacity(Object[] elementData, int minCapacity) { //如果傳入的是個空數(shù)組則最小容量取默認(rèn)容量與minCapacity之間的最大值 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }private void ensureExplicitCapacity(int minCapacity) { modCount++; // 如果最小需要空間比elementData的內(nèi)存空間要大,則需要擴(kuò)容 // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }private void grow(int minCapacity) { // 獲取elementData數(shù)組的內(nèi)存空間長度 int oldCapacity = elementData.length; // 擴(kuò)容至原來的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); //校驗容量是否夠 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //若預(yù)設(shè)值大于默認(rèn)的最大值,檢查是否溢出 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 調(diào)用Arrays.copyOf方法將elementData數(shù)組指向新的內(nèi)存空間 //并將elementData的數(shù)據(jù)復(fù)制到新的內(nèi)存空間 elementData = Arrays.copyOf(elementData, newCapacity); }28. HashMap 的長度為什么是2的冪次方,以及其他常量定義的含義~
為了能讓HashMap存取高效,數(shù)據(jù)分配均勻。
看著呢,以下等式相等,但是位移運算比取余效率高很多呢~
hash%length=hash&(length-1)29. ConcurrenHashMap 原理?1.8 中為什么要用紅黑樹?
聊到ConcurrenHashMap,需要跟面試官聊到安全性,分段鎖segment,為什么放棄了分段鎖,與及選擇CAS,其實就是都是從效率和安全性觸發(fā),嘻嘻~
java8不是用紅黑樹來管理hashmap,而是在hash值相同的情況下(且重復(fù)數(shù)量大于8),用紅黑樹來管理數(shù)據(jù)。紅黑樹相當(dāng)于排序數(shù)據(jù)。可以自動的使用二分法進(jìn)行定位。性能較高。30. ArrayList的默認(rèn)大小
ArrayList 的默認(rèn)大小是 10 個元素
/** * Default initial capacity. */private static final int DEFAULT_CAPACITY = 10;31. 為何Collection不從Cloneable和Serializable接口繼承?
Collection表示一個集合,包含了一組對象元素。如何維護(hù)它的元素對象是由具體實現(xiàn)來決定的。因為集合的具體形式多種多樣,例如list允許重復(fù),set則不允許。而克隆(clone)和序列化(serializable)只對于具體的實體,對象有意義,你不能說去把一個接口,抽象類克隆,序列化甚至反序列化。所以具體的collection實現(xiàn)類是否可以克隆,是否可以序列化應(yīng)該由其自身決定,而不能由其超類強行賦予。
如果collection繼承了clone和serializable,那么所有的集合實現(xiàn)都會實現(xiàn)這兩個接口,而如果某個實現(xiàn)它不需要被克隆,甚至不允許它序列化(序列化有風(fēng)險),那么就與collection矛盾了。
32. Enumeration和Iterator接口的區(qū)別?
public interface Enumeration<E> { boolean hasMoreElements(); E nextElement();}public interface Iterator<E> { boolean hasNext(); E next(); void remove();}函數(shù)接口不同
Enumeration速度快,占用內(nèi)存少,但是不是快速失敗的,線程不安全。
Iterator允許刪除底層數(shù)據(jù),枚舉不允許
Iterator安全性高,因為其他線程不能夠修改正在被Iterator遍歷的集合里面的對象。
33. 我們?nèi)绾螌σ唤M對象進(jìn)行排序?
可以用 Collections.sort()+ Comparator.comparing(),因為對對象排序,實際上是對對象的屬性排序哈~
public class Student {private String name; private int score;public Student(String name, int score){ this.name = name; this.score = score; }public String getName() { return name; }public void setName(String name) { this.name = name; }public int getScore() { return score; }public void setScore(int score) { this.score = score; }@Override public String toString() { return "Student: " + this.name + " 分?jǐn)?shù):" + Integer.toString( this.score ); }} public class Test {public static void main(String[] args) {List<Student> studentList = new ArrayList<>(); studentList.add(new Student("D", 90)); studentList.add(new Student("C", 100)); studentList.add(new Student("B", 95)); studentList.add(new Student("A", 95));Collections.sort(studentList, Comparator.comparing(Student::getScore).reversed().thenComparing(Student::getName)); studentList.stream().forEach(p -> System.out.println(p.toString())); }}34. 當(dāng)一個集合被作為參數(shù)傳遞給一個函數(shù)時,如何才可以確保函數(shù)不能修改它?
這個跟之前那個不可變集合一樣道理哈~
在作為參數(shù)傳遞之前,使用Collections.unmodifiableCollection(Collection c)方法創(chuàng)建一個只讀集合,這將確保改變集合的任何操作都會拋出UnsupportedOperationException。
35. 說一下HashSet的實現(xiàn)原理?
不能保證元素的排列順序,順序有可能發(fā)生變化。
元素可以為null
hashset保證元素不重復(fù)~ (這個面試官很可能會問什么原理,這個跟HashMap有關(guān)的哦)
HashSet,需要談?wù)勊鼈zhashcode()和equles()哦~
實際是基于HashMap實現(xiàn)的,HashSet 底層使用HashMap來保存所有元素的
看看它的add方法吧~
public boolean add(E e) { return map.put(e, PRESENT)==null; }36. Array 和 ArrayList 有何區(qū)別?
定義一個 Array 時,必須指定數(shù)組的數(shù)據(jù)類型及數(shù)組長度,即數(shù)組中存放的元素個數(shù)固定并且類型相同。
ArrayList 是動態(tài)數(shù)組,長度動態(tài)可變,會自動擴(kuò)容。不使用泛型的時候,可以添加不同類型元素。
37. 為什么HashMap中String、Integer這樣的包裝類適合作為key?
String、Integer等包裝類的特性能夠保證Hash值的不可更改性和計算準(zhǔn)確性,能夠有效的減少Hash碰撞的幾率~
因為
它們都是final修飾的類,不可變性,保證key的不可更改性,不會存在獲取hash值不同的情況~
它們內(nèi)部已重寫了equals()、hashCode()等方法,遵守了HashMap內(nèi)部的規(guī)范
38. 如果想用Object作為hashMap的Key?;
重寫hashCode()和equals()方法啦~ (這個答案來自互聯(lián)網(wǎng)哈~)
重寫hashCode()是因為需要計算存儲數(shù)據(jù)的存儲位置,需要注意不要試圖從散列碼計算中排除掉一個對象的關(guān)鍵部分來提高性能,這樣雖然能更快但可能會導(dǎo)致更多的Hash碰撞;
重寫equals()方法,需要遵守自反性、對稱性、傳遞性、一致性以及對于任何非null的引用值x,x.equals(null)必須返回false的這幾個特性,目的是為了保證key在哈希表中的唯一性;
39. 講講紅黑樹的特點?
每個節(jié)點或者是黑色,或者是紅色。
根節(jié)點是黑色。
每個葉子節(jié)點(NIL)是黑色。[注意:這里葉子節(jié)點,是指為空(NIL或NULL)的葉子節(jié)點!]
如果一個節(jié)點是紅色的,則它的子節(jié)點必須是黑色的。
從一個節(jié)點到該節(jié)點的子孫節(jié)點的所有路徑上包含相同數(shù)目的黑節(jié)點。
40. Java集合類框架的最佳實踐有哪些?
其實這些點,結(jié)合平時工作,代碼總結(jié)講出來,更容易吸引到面試官呢 (這個答案來自互聯(lián)網(wǎng)哈~)
1.根據(jù)應(yīng)用需要正確選擇要使用的集合類型對性能非常重要,比如:假如知道元素的大小是固定的,那么選用Array類型而不是ArrayList類型更為合適。
2.有些集合類型允許指定初始容量。因此,如果我們能估計出存儲的元素的數(shù)目,我們可以指定初始容量來避免重新計算hash值或者擴(kuò)容等。
3.為了類型安全、可讀性和健壯性等原因總是要使用泛型。同時,使用泛型還可以避免運行時的ClassCastException。
4.使用JDK提供的不變類(immutable class)作為Map的鍵可以避免為我們自己的類實現(xiàn)hashCode()和equals()方法。
5.編程的時候接口優(yōu)于實現(xiàn)
6.底層的集合實際上是空的情況下,返回為長度是0的集合或數(shù)組而不是null。
41.談?wù)劸€程池阻塞隊列吧~
ArrayBlockingQueue
LinkedBlockingQueue
DelayQueue
PriorityBlockingQueue
SynchronousQueue
ArrayBlockingQueue: (有界隊列)是一個用數(shù)組實現(xiàn)的有界阻塞隊列,按FIFO排序量。
LinkedBlockingQueue: (可設(shè)置容量隊列)基于鏈表結(jié)構(gòu)的阻塞隊列,按FIFO排序任務(wù),容量可以選擇進(jìn)行設(shè)置,不設(shè)置的話,將是一個無邊界的阻塞隊列,最大長度為Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool線程池使用了這個隊列
DelayQueue:(延遲隊列)是一個任務(wù)定時周期的延遲執(zhí)行的隊列。根據(jù)指定的執(zhí)行時間從小到大排序,否則根據(jù)插入到隊列的先后排序。newScheduledThreadPool線程池使用了這個隊列。
PriorityBlockingQueue:(優(yōu)先級隊列)是具有優(yōu)先級的無界阻塞隊列;
SynchronousQueue:(同步隊列)一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調(diào)用移除操作,否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool線程池使用了這個隊列。針對面試題:線程池都有哪幾種工作隊列?
我覺得,回答以上幾種ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue等,說出它們的特點,并結(jié)合使用到對應(yīng)隊列的常用線程池(如newFixedThreadPool線程池使用LinkedBlockingQueue),進(jìn)行展開闡述, 就可以啦。
42. HashSet和TreeSet有什么區(qū)別?
Hashset 的底層是由哈希表實現(xiàn)的,Treeset 底層是由紅黑樹實現(xiàn)的。
HashSet中的元素沒有順序,TreeSet保存的元素有順序性(實現(xiàn)Comparable接口)
HashSet的add(),remove(),contains()方法的時間復(fù)雜度是O(1);TreeSet中,add(),remove(),contains()方法的時間復(fù)雜度是O(logn)
43. Set里的元素是不能重復(fù)的,那么用什么方法來區(qū)分重復(fù)與否呢? 是用==還是equals()?
元素重復(fù)與否是使用equals()方法進(jìn)行判斷的,這個可以跟面試官說說==和equals()的區(qū)別,hashcode()和equals
44. 說出ArrayList,LinkedList的存儲性能和特性
這道面試題,跟ArrayList,LinkedList,就是換湯不換藥的~
ArrayList,使用數(shù)組方式存儲數(shù)據(jù),查詢時,ArrayList是基于索引(index)的數(shù)據(jù)結(jié)構(gòu),可以直接映射到,速度較快;但是插入數(shù)據(jù)需要移動數(shù)據(jù),效率就比LinkedList慢一點~
LinkedList,使用雙向鏈表實現(xiàn)存儲,按索引數(shù)據(jù)需要進(jìn)行前向或后向遍歷,查詢相對ArrayList慢一點;但是插入數(shù)據(jù)速度較快。
LinkedList比ArrayList開銷更大,因為LinkedList的節(jié)點除了存儲數(shù)據(jù),還需要存儲引用。
45. HashMap在JDK1.7和JDK1.8中有哪些不同?
互聯(lián)網(wǎng)上這個答案太詳細(xì)啦(https://www.jianshu.com/p/939b8a672070)
46. ArrayList集合加入1萬條數(shù)據(jù),應(yīng)該怎么提高效率
因為ArrayList的底層是數(shù)組實現(xiàn),并且數(shù)組的默認(rèn)值是10,如果插入10000條要不斷的擴(kuò)容,耗費時間,所以我們調(diào)用ArrayList的指定容量的構(gòu)造器方法ArrayList(int size) 就可以實現(xiàn)不擴(kuò)容,就提高了性能。
47. 如何對Object的list排序
看例子吧,哈哈,這個跟對象排序也是一樣的呢~
public class Person {private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Person(String name, Integer age) { this.name = name; this.age = age; }} public class Test {public static void main(String[] args) {List<Person> list = new ArrayList<>(); list.add(new Person("jay", 18)); list.add(new Person("tianLuo", 10));list.stream().forEach(p -> System.out.println(p.getName()+" "+p.getAge())); // 用comparing比較對象屬性 list.sort(Comparator.comparing(Person::getAge));System.out.println("排序后");list.stream().forEach(p -> System.out.print(p.getName()+" "+p.getAge()+" ")); }}48. ArrayList 和 HashMap 的默認(rèn)大小是多數(shù)?
在 Java 7 中,ArrayList 的默認(rèn)大小是 10 個元素,HashMap 的默認(rèn)大小是16個元素(必須是2的冪)。
49. 有沒有有順序的Map實現(xiàn)類,如果有,他們是怎么保證有序的
Hashmap和Hashtable 都不是有序的。
TreeMap和LinkedHashmap都是有序的。(TreeMap默認(rèn)是key升序,LinkedHashmap默認(rèn)是數(shù)據(jù)插入順序)
TreeMap是基于比較器Comparator來實現(xiàn)有序的。
LinkedHashmap是基于鏈表來實現(xiàn)數(shù)據(jù)插入有序的。
50. HashMap是怎么解決哈希沖突的
Hashmap解決hash沖突,使用的是鏈地址法,即數(shù)組+鏈表的形式來解決。put執(zhí)行首先判斷table[i]位置,如果為空就直接插入,不為空判斷和當(dāng)前值是否相等,相等就覆蓋,如果不相等的話,判斷是否是紅黑樹節(jié)點,如果不是,就從table[i]位置開始遍歷鏈表,相等覆蓋,不相等插入
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號
好文章,我在看??
總結(jié)
以上是生活随笔為你收集整理的50道Java集合经典面试题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么?你项目还在用Date表示时间?!
- 下一篇: NYOJ练习题 又见Alice and