Android面試之Java中級篇

一隻有交流障礙的醜程式猿發表於2018-03-17

本文是Android面試題整理中的一篇,結合右下角目錄食用更佳,包括:

  • 集合
  • 記憶體
  • 垃圾回收

集合


0. List和Set的區別

  1. 它們都是介面,都實現了Collection介面
  2. List元素可以重複,元素順序與插入順序相同,其子類有LinkedList和ArrayList
  3. Set元素不能重複,元素順序與插入順序不同,子類有HashSet(通過HashMap實現),LinkedHashSet,TreeSet(紅黑樹實現,排序的)

1. List、Map、Set三個介面存取元素時,各有什麼特點?

  1. List繼承了Collection,儲存值,可以有重複值
  2. Set繼承了Collection,儲存值,不能有重複值
  3. Map儲存健值對,可以一對一或一對多

2. List、Set、Map是否繼承自Collection介面

List、Set 是,Map 不是。Map是鍵值對對映容器,與List和Set有明顯的區別,而Set儲存的零散的元素且不允許有重複元素(數學中的集合也是如此),List是線性結構的容器,適用於按數值索引訪問元素的情形

3. HashMap實現原理

  1. 在1.7中,HashMap採用陣列+單連結串列的結構;1.8中,採用陣列+單連結串列或紅黑樹的結構(當連結串列size > 8時,轉換成紅黑樹)
  2. HaspMap中有兩個關鍵的建構函式,一個是初始容量,另一個是負載因子。
  3. 初始容量即陣列的初始大小,當map中元素個數 > 初始容量*負載因子時,HashMap呼叫resize()方法擴容
  4. 在存入資料時,對key的hashCode再次進行hash(),目的是讓hash值分佈均勻
  5. 對hash() 返回的值與容量進行與運算,確定在陣列中的位置
  6. key可以為null,null的hash值是0

4. HashMap是怎麼解決hash衝突的

  1. 對hashCode在呼叫hash()方法進行計算
  2. 當超過閾值時進行擴容
  3. 當發生衝突時使用連結串列或者紅黑樹解決衝突

5. 為什麼不直接採用經過hashCode()處理的雜湊碼作為儲存陣列table的下標位置

  1. hashCode可能很大,陣列初始容量可能很小,不匹配,所以需要: hash碼 & (陣列長度-1)作為陣列下標
  2. hashCode可能分佈的不均勻

6. 為什麼在計算陣列下標前,需對雜湊碼進行二次處理:擾動處理?

加大雜湊碼低位的隨機性,使得分佈更均勻,從而提高對應陣列儲存下標位置的隨機性 & 均勻性,最終減少Hash衝突

7. 為什麼說HashMap不保證有序,儲存位置會隨時間變化

  1. 通過hash值確定位置,與使用者插入順序不同
  2. 在達到閾值後,HashMap會呼叫resize方法擴容,擴容後位置發生變化

8. HashMap的時間複雜度

HashMap通過陣列和連結串列實現,陣列查詢的時間複雜度是O(1),連結串列的時間複雜度是O(n),所以要讓HashMap儘可能的塊,就需要連結串列的長度儘可能的小,當連結串列長度是1是,HashMap的時間複雜度就變成了O(1);根據HashMap的實現原理,要想讓連結串列長度儘可能的短,需要hash演算法儘量減少衝突。

9. HashMap 中的 key若 Object型別, 則需實現哪些方法

hashCode和equals

10. 為什麼 HashMap 中 String、Integer 這樣的包裝類適合作為 key 鍵

  1. 它們是final的,不可變,保證了安全性
  2. 均已經實現了hashCode和equals方法,計算準確

11. HashMap執行緒安全嗎

  1. HashMap執行緒不安全
  2. HashMap沒有同步鎖,舉例:比如A、B兩個執行緒同時觸發擴容,HashMap容量增加2倍,並將原資料重新分配到新的位置,這個時候可能出現原連結串列轉移到新連結串列時生成了環形連結串列,出現死迴圈。

12. HashMap執行緒安全的解決方案

  1. 使用Collections.synchronizedMap()方法,該方法的實現方式是使用synchronized關鍵字
  2. 使用ConcurrentHashMap,效能比Collections.synchronizedMap()更好

13. HashMap 如何刪除元素

  1. 計算key的Hash值,找到陣列中的位置,得到連結串列的頭指標
  2. 遍歷連結串列,通過equals比較key,確定是不是要找的元素
  3. 找到後調整連結串列,將該元素從連結串列中刪除
//原始碼 java8
 @Override public V remove(Object key) {
        if (key == null) {
            return removeNullKey();
        }
        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        for (HashMapEntry<K, V> e = tab[index], prev = null;
                e != null; prev = e, e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                if (prev == null) {
                    tab[index] = e.next;
                } else {
                    prev.next = e.next;
                }
                modCount++;
                size--;
                postRemove(e);
                return e.value;
            }
        }
        return null;
    }
複製程式碼

14. HashMap的擴容過程

  1. 在初次載入時,會呼叫resize()進行初始化
  2. 當put()時,會檢視當前元素個數是否大於閾值(閾值=陣列長度*負載因子),當大於時,呼叫resize方法擴容
  3. 新建一個陣列,擴容後的容量是原來的兩倍
  4. 將原來的資料重新計算位置,拷貝到新的table上

15. java8 中HashMap的優化

最大變化是當連結串列超過一定的長度後,將連結串列轉換成紅黑樹儲存,在儲存很多資料時,效率提升了。連結串列的查詢複雜度是O(n),紅黑樹是O(log(n))

16. HashMap和HashTable的區別

  1. HashTable是執行緒安全的,而HashMap不是
  2. HashMap中允許存在null鍵和null值,而HashTable中不允許
  3. HashTable已經棄用,我們可以用ConcurrentHashMap等替代

17. ConcurrentHashMap實現原理

  1. ConcurrentHashMap是執行緒安全的HashMap,效率比直接加cynchronized要好
  2. 1.7中通過分段鎖實現,讀不枷鎖(通過volatile保證可見性),寫時給對應的分段加鎖。(1.8實現原理變了)

18. ConcurrentHashMap的併發度是什麼

  1. ConcurrentHashMap通過分段鎖來實現,併發度即為段數
  2. 段數是ConcurrentHashMap類建構函式的一個可選引數,預設值為16

19. LinkedHashMap原理

  1. LinkedHashMap通過繼承HashMap實現,既保留了HashMap快速查詢能力,又儲存了存入順序
  2. LinkedHashMap重寫了HashMap的Entry,通過LinkedEntry儲存了存入順序,可以理解為通過雙向連結串列和HashMap共同實現

20. HashMap和Arraylist都是執行緒不安全的,怎麼讓他們執行緒安全

  1. 藉助Collections工具類synchronizedMap和synchronizedList將其轉為執行緒安全的
  2. 使用安全的類替代,如HashTable(不建議使用)或者ConcurrentHashMap替代Hashmap,用CopyOnWriteArrayList替代ArrayList

21. HashSet 是如何保證不重複的

  1. HashSet 是通過HashMap來實現的,內部持有一個HashMap例項
  2. HashSet存入的值即為HashMap的key,hashMap的value是HashSet中new 的一個Object例項,所有的value都相同

22. TreeSet 兩種排序方式

  1. 自然排序:呼叫無參建構函式,新增的元素必須實現Comparable介面
  2. 定製排序:使用有參建構函式,傳入一個Comparator例項

23. Array 和 ArrayList對比

  1. Array可以是基本型別和引用型別,ArrayList只能是引用型別
  2. Array固定大小,ArrayList長度可以動態改變
  3. ArrayList有很多方法可以呼叫,如addAll()

24. List和陣列的互相轉換/String轉換成陣列

  1. String[] a = list.toArray(new String[size]));
  2. List list = Arrays.asList(array);
  3. char[] char = string.toCharArray();

25. 陣列在記憶體中是如何分配的

和引用型別一樣,在棧中儲存一個引用,指向堆地址

26. TreeMap和TreeSet在排序時如何比較元素?Collections工具類中的sort()方法如何比較元素?

  1. TreeMap和TreeSet都是通過紅黑樹實現的,因此要求元素都是可比較的,元素必須實現Comparable介面,該介面中有compareTo()方法。
  2. Collections的sort方法有兩種過載形式,一種只有一個引數,要求傳入的待排序元素必須實現Comparable介面;第二種有兩個引數,要求傳入待排序容器即Comparator例項

27. 闡述ArrayList、Vector、LinkedList的儲存效能和特性。

  1. ArrayList 和Vector都是使用陣列方式儲存資料,陣列方式節省空間,便與讀取;但插入刪除涉及陣列移動,效能較差。
  2. Vector中的方法由於新增了synchronized修飾,因此Vector是執行緒安全的容器。
  3. LinkedList使用雙向連結串列實現儲存,佔用空間大,讀取慢;插入刪除快
  4. Vector已經被遺棄,不推薦使用。為了實現執行緒安全的list,可以使用Collections中的synchronizedList方法將其轉換成執行緒安全的容器後再使用

28. 什麼是Java優先順序佇列(Priority Queue)

PriorityQueue是一個基於優先順序堆的無界佇列,它的元素是按照自然順序(natural order)排序的。在建立的時候,我們可以給它提供一個負責給元素排序的比較器。PriorityQueue不允許null值,因為他們沒有自然順序,或者說他們沒有任何的相關聯的比較器。PriorityQueue的邏輯結構為堆(完全二叉樹),物理結構為陣列。最後,PriorityQueue不是執行緒安全的,入隊和出隊的時間複雜度是O(log(n))

29. List、Set、Map的遍歷方式

  1. List、Set都繼承了Collection介面,可以使用for each遍歷或者Iterator
  2. HashMap可以拿到KeySet或者entrySet,然後用iterator或者 for each 方式遍歷

30. 什麼是Iterator(迭代器)

迭代器是一個介面,我們可以藉助這個介面實現對集合的遍歷,刪除 擴充套件(迭代器實現原理):Collection繼承了Iterable介面,iterable介面中有iterator方法,返回一個Iterator迭代器 -> Collection的實現類通過在內部實現自定義Iterator,在iterator時返回這個例項。

31. Iterator和ListIterator的區別是什麼

  1. ListIterator 繼承自 Iterator
  2. Iterator可用來遍歷Set和List集合,但是ListIterator只能用來遍歷List
  3. ListIterator比Iterator增加了更多功能,例如可以雙向遍歷,增加元素等

32. 如何權衡是使用無序的陣列還是有序的陣列

  1. 查詢多用有序陣列,插入刪除多用無序陣列
  2. 解釋:有序陣列最大的好處在於查詢的時間複雜度是O(log n),而無序陣列是O(n)。有序陣列的缺點是插入操作的時間複雜度是O(n),因為值大的元素需要往後移動來給新元素騰位置。相反,無序陣列的插入時間複雜度是常量O(1)

33. Arrays.sort 實現原理和 Collections.sort 實現原理

  1. Collections.sort是通過Arrays.sort實現的。當list不為ArrayList時,先轉成陣列,再呼叫Arrays.sort
  2. java 8 中Array.Sort()是通過timsort(一種優化的歸併排序)來實現的

34. Collection和Collections的區別

  1. Collection 是一個介面,Set、List都繼承了該介面
  2. Collections 是一個工具類,該工具類可以幫我們完成對容器的判空,排序,執行緒安全化等。

35. 快速失敗(fail-fast)和安全失敗(fail-safe)

  1. 快速失敗:在用迭代器遍歷一個集合物件時,如果遍歷過程中對集合物件的內容進行了修改(增加、刪除、修改),則會丟擲Concurrent Modification Exception
  2. 安全失敗:操作是物件的副本,這個時候原物件改變並不會對當前迭代器遍歷產生影響。java.util.concurrent類下邊容器都是安全失敗
    擴充套件:快速失敗原理:容器內部有一個modCount,記錄變化的次數,當進行遍歷時,如果mocount值發生改變,責快速失敗

36. 當一個集合被作為引數傳遞給一個函式時,如何才可以確保函式不能修改它

在作為引數傳遞之前,我們可以使用Collections.unmodifiableCollection(Collection c)方法建立一個只讀集合,這將確保改變集合的任何操作都會丟擲UnsupportedOperationException。

記憶體


0. 記憶體中的棧(stack)、堆(heap)和方法區(method area)?

  1. 棧:執行緒獨有,每個執行緒一個棧區。儲存基本資料型別,物件的引用,函式呼叫的現場(棧可以分為三個部分:基本型別,執行環境上下文,操作指令區(存放操作指令));優點是速度快,缺點是大小和生存週期必須是確定的
  2. 堆:執行緒共享,jvm一共一個堆區。儲存物件的例項,垃圾回收器回收的是堆區的記憶體
  3. 方法區(靜態區):執行緒共享。儲存類資訊、常量、靜態變數、JIT編譯器編譯後的程式碼等資料,常量池是方法區的一部分。

1. Jvm記憶體模型

Android面試之Java中級篇

  1. 堆:執行緒共享,存放物件例項,所有的物件的記憶體都在這裡分配。垃圾回收主要就是作用於這裡的。
  2. java虛擬機器棧:執行緒私有,生命週期與執行緒相同。每個方法執行的時候都會建立一個棧幀(stack frame)用於存放 區域性變數表、操作棧、動態連結、方法出口
  3. native方法棧
  4. 程式計數器:這裡記錄了執行緒執行的位元組碼的行號,在分支、迴圈、跳轉、異常、執行緒恢復等都依賴這個計數器。
  5. 方法區:執行緒共享的儲存了每個類物件的資訊(包括類的名稱、方法資訊、欄位資訊)、靜態變數、常量以及編譯器編譯後的程式碼等。

垃圾回收


0. java中存在記憶體洩漏嗎

  1. java中存在記憶體洩漏
  2. java中雖然有GC幫我們自動回收記憶體,但是隻有當例項沒有引用指向它時才會被回收,若我們錯誤的持有了引用,沒有在應當釋放時釋放,就會造成記憶體洩漏,例如在長生命週期物件持有短生命週期物件。
舉例:
import java.util.Arrays;
import java.util.EmptyStackException;

public class MyStack<T> {
private T[] elements;
private int size = 0;

private static final int INIT_CAPACITY = 16;

public MyStack() {
   elements = (T[]) new Object[INIT_CAPACITY];
 }

public void push(T elem) {
   ensureCapacity();
   elements[size++] = elem;
 }

public T pop() {
if(size == 0)
   throw new EmptyStackException();
   return elements[--size];
   }

private void ensureCapacity() {
 if(elements.length == size) {
   elements = Arrays.copyOf(elements, 2 * size + 1);
  }
 }
}

分析:這裡用陣列實現了一個棧,但是當資料pop之後,陣列裡內容並沒有被清空。
複製程式碼

1. GC是什麼?為什麼要有GC?

  1. GC是垃圾收集器(Garbage Collection)的縮寫,是面試中常考的點。瞭解GC的執行方式,對防止記憶體洩漏,提高執行效率等都有好處
  2. 垃圾收集器會自動進行記憶體回收,不需要程式設計師進行操作,System.gc() 或Runtime.getRuntime().gc() 時並不是馬上進行記憶體回收,甚至不會進行記憶體回收
  3. 詳細參見JVM的記憶體回收機制

2. 如何定義垃圾

  1. 引用計數(無法解決迴圈引用的問題)
  2. 可達性分析

3. 什麼變數能作為GCRoot

  1. 虛擬機器棧(棧幀中的本地變數表)中引用的物件;
  2. 方法區中的類靜態屬性引用的物件
  3. 方法區中的常量引用的物件
  4. 原生方法棧(Native Method Stack)中 JNI 中引用的物件。

4. 垃圾回收的方法

  1. 標記-清除(Mark-Sweep)法:減少停頓時間,但會造成記憶體碎片
  2. 標記-整理(Mark-Compact)法:可以解決記憶體碎片問題,但是會增加停頓時間
  3. 複製(copying)法:從一個地方拷貝到另一個地方,適合有大量回收的場景,比如新生代回收
  4. 分代收集:把記憶體區域分成不同代,根據代不同採取不同的策略
    新生代(Yong Generation):存放新建立的物件,採用複製回收方法;年老代(old Generation):這些物件垃圾回收的頻率較低,採用的標記整理方法,這裡的垃圾回收叫做 major GC;永久代(permanent Generation):存放Java本身的一些資料,當類不再使用時,也會被回收。

5. JVM垃圾回收何時觸發MinorGC等操作

  1. minorGc發生在年輕代,是複製回收
  2. 年輕代可以分為三個區域:Eden、from Survivor和to Survivor;當Eden滿了的時候,觸發minorGc
  3. Gc過程:Eden區複製到to區;from區年齡大的被移到年老區,年齡小的複製到to區;to區變成from區;

6. JVM 年輕代到年老代的晉升過程的判斷條件是什麼

  1. 在年輕代gc過程中存活的次數超過閾值
  2. 或者太大了直接放入年老代
  3. to Survivor滿了,新物件直接放入老年代
  4. 還有一種情況,如果在From空間中,相同年齡所有物件的大小總和大於From和To空間總和的一半,那麼年齡大於等於該年齡的物件就會被移動到老年代,而不用等到15歲(預設)

7. Full GC 觸發的條件

  1. 呼叫System.gc時,系統建議執行Full GC,但是不必然執行
  2. 老年代或者永久代空間不足
  3. 其他(=-=)

8. OOM錯誤,stackoverflow錯誤,permgen space錯誤

  1. OOM 是堆記憶體溢位
  2. stackoverflow是棧記憶體溢位
  3. permgen space說的是溢位的區域在永久代

9. 記憶體溢位的種類

  1. stackoverflow:;當執行緒呼叫一個方法是,jvm壓入一個新的棧幀到這個執行緒的棧中,只要這個方法還沒返回,這個棧幀就存在。如果方法的巢狀呼叫層次太多(如遞迴呼叫),隨著java棧中的幀的增多,最終導致這個執行緒的棧中的所有棧幀的大小的總和大於-Xss設定的值,而產生生StackOverflowError溢位異常
  2. outofmemory:
    1. java程式啟動一個新執行緒時,沒有足夠的空間為改執行緒分配java棧,一個執行緒java棧的大小由-Xss設定決定;JVM則丟擲OutOfMemoryError異常;
    2. java堆用於存放物件的例項,當需要為物件的例項分配記憶體時,而堆的佔用已經達到了設定的最大值(通過-Xmx)設定最大值,則丟擲OutOfMemoryError異常;
    3. 方法區用於存放java類的相關資訊,如類名、訪問修飾符、常量池、欄位描述、方法描述等。在類載入器載入class檔案到記憶體中的時候,JVM會提取其中的類資訊,並將這些類資訊放到方法區中。當需要儲存這些類資訊,而方法區的記憶體佔用又已經達到最大值(通過-XX:MaxPermSize);將會丟擲OutOfMemoryError異常

參考資料

Java面試題全集(上)

http://www.jcodecraeer.com/a/chengxusheji/java/2015/0520/2896.html 本文是Android面試題整理中的一篇,結合右下角目錄食用更佳,包括:

  • 集合
  • 記憶體
  • 垃圾回收

集合


0. List和Set的區別

  1. 它們都是介面,都實現了Collection介面
  2. List元素可以重複,元素順序與插入順序相同,其子類有LinkedList和ArrayList
  3. Set元素不能重複,元素順序與插入順序不同,子類有HashSet(通過HashMap實現),LinkedHashSet,TreeSet(紅黑樹實現,排序的)

1. List、Map、Set三個介面存取元素時,各有什麼特點?

  1. List繼承了Collection,儲存值,可以有重複值
  2. Set繼承了Collection,儲存值,不能有重複值
  3. Map儲存健值對,可以一對一或一對多

2. List、Set、Map是否繼承自Collection介面

List、Set 是,Map 不是。Map是鍵值對對映容器,與List和Set有明顯的區別,而Set儲存的零散的元素且不允許有重複元素(數學中的集合也是如此),List是線性結構的容器,適用於按數值索引訪問元素的情形

3. HashMap實現原理

  1. 在1.7中,HashMap採用陣列+單連結串列的結構;1.8中,採用陣列+單連結串列或紅黑樹的結構(當連結串列size > 8時,轉換成紅黑樹)
  2. HaspMap中有兩個關鍵的建構函式,一個是初始容量,另一個是負載因子。
  3. 初始容量即陣列的初始大小,當map中元素個數 > 初始容量*負載因子時,HashMap呼叫resize()方法擴容
  4. 在存入資料時,對key的hashCode再次進行hash(),目的是讓hash值分佈均勻
  5. 對hash() 返回的值與容量進行與運算,確定在陣列中的位置
  6. key可以為null,null的hash值是0

4. HashMap是怎麼解決hash衝突的

  1. 對hashCode在呼叫hash()方法進行計算
  2. 當超過閾值時進行擴容
  3. 當發生衝突時使用連結串列或者紅黑樹解決衝突

5. 為什麼不直接採用經過hashCode()處理的雜湊碼作為儲存陣列table的下標位置

  1. hashCode可能很大,陣列初始容量可能很小,不匹配,所以需要: hash碼 & (陣列長度-1)作為陣列下標
  2. hashCode可能分佈的不均勻

6. 為什麼在計算陣列下標前,需對雜湊碼進行二次處理:擾動處理?

加大雜湊碼低位的隨機性,使得分佈更均勻,從而提高對應陣列儲存下標位置的隨機性 & 均勻性,最終減少Hash衝突

7. 為什麼說HashMap不保證有序,儲存位置會隨時間變化

  1. 通過hash值確定位置,與使用者插入順序不同
  2. 在達到閾值後,HashMap會呼叫resize方法擴容,擴容後位置發生變化

8. HashMap的時間複雜度

HashMap通過陣列和連結串列實現,陣列查詢的時間複雜度是O(1),連結串列的時間複雜度是O(n),所以要讓HashMap儘可能的塊,就需要連結串列的長度儘可能的小,當連結串列長度是1是,HashMap的時間複雜度就變成了O(1);根據HashMap的實現原理,要想讓連結串列長度儘可能的短,需要hash演算法儘量減少衝突。

9. HashMap 中的 key若 Object型別, 則需實現哪些方法

hashCode和equals

10. 為什麼 HashMap 中 String、Integer 這樣的包裝類適合作為 key 鍵

  1. 它們是final的,不可變,保證了安全性
  2. 均已經實現了hashCode和equals方法,計算準確

11. HashMap執行緒安全嗎

  1. HashMap執行緒不安全
  2. HashMap沒有同步鎖,舉例:比如A、B兩個執行緒同時觸發擴容,HashMap容量增加2倍,並將原資料重新分配到新的位置,這個時候可能出現原連結串列轉移到新連結串列時生成了環形連結串列,出現死迴圈。

12. HashMap執行緒安全的解決方案

  1. 使用Collections.synchronizedMap()方法,該方法的實現方式是使用synchronized關鍵字
  2. 使用ConcurrentHashMap,效能比Collections.synchronizedMap()更好

13. HashMap 如何刪除元素

  1. 計算key的Hash值,找到陣列中的位置,得到連結串列的頭指標
  2. 遍歷連結串列,通過equals比較key,確定是不是要找的元素
  3. 找到後調整連結串列,將該元素從連結串列中刪除
//原始碼 java8
 @Override public V remove(Object key) {
        if (key == null) {
            return removeNullKey();
        }
        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        for (HashMapEntry<K, V> e = tab[index], prev = null;
                e != null; prev = e, e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                if (prev == null) {
                    tab[index] = e.next;
                } else {
                    prev.next = e.next;
                }
                modCount++;
                size--;
                postRemove(e);
                return e.value;
            }
        }
        return null;
    }
複製程式碼

14. HashMap的擴容過程

  1. 在初次載入時,會呼叫resize()進行初始化
  2. 當put()時,會檢視當前元素個數是否大於閾值(閾值=陣列長度*負載因子),當大於時,呼叫resize方法擴容
  3. 新建一個陣列,擴容後的容量是原來的兩倍
  4. 將原來的資料重新計算位置,拷貝到新的table上

15. java8 中HashMap的優化

最大變化是當連結串列超過一定的長度後,將連結串列轉換成紅黑樹儲存,在儲存很多資料時,效率提升了。連結串列的查詢複雜度是O(n),紅黑樹是O(log(n))

16. HashMap和HashTable的區別

  1. HashTable是執行緒安全的,而HashMap不是
  2. HashMap中允許存在null鍵和null值,而HashTable中不允許
  3. HashTable已經棄用,我們可以用ConcurrentHashMap等替代

17. ConcurrentHashMap實現原理

  1. ConcurrentHashMap是執行緒安全的HashMap,效率比直接加cynchronized要好
  2. 1.7中通過分段鎖實現,讀不枷鎖(通過volatile保證可見性),寫時給對應的分段加鎖。(1.8實現原理變了)

18. ConcurrentHashMap的併發度是什麼

  1. ConcurrentHashMap通過分段鎖來實現,併發度即為段數
  2. 段數是ConcurrentHashMap類建構函式的一個可選引數,預設值為16

19. LinkedHashMap原理

  1. LinkedHashMap通過繼承HashMap實現,既保留了HashMap快速查詢能力,又儲存了存入順序
  2. LinkedHashMap重寫了HashMap的Entry,通過LinkedEntry儲存了存入順序,可以理解為通過雙向連結串列和HashMap共同實現

20. HashMap和Arraylist都是執行緒不安全的,怎麼讓他們執行緒安全

  1. 藉助Collections工具類synchronizedMap和synchronizedList將其轉為執行緒安全的
  2. 使用安全的類替代,如HashTable(不建議使用)或者ConcurrentHashMap替代Hashmap,用CopyOnWriteArrayList替代ArrayList

21. HashSet 是如何保證不重複的

  1. HashSet 是通過HashMap來實現的,內部持有一個HashMap例項
  2. HashSet存入的值即為HashMap的key,hashMap的value是HashSet中new 的一個Object例項,所有的value都相同

22. TreeSet 兩種排序方式

  1. 自然排序:呼叫無參建構函式,新增的元素必須實現Comparable介面
  2. 定製排序:使用有參建構函式,傳入一個Comparator例項

23. Array 和 ArrayList對比

  1. Array可以是基本型別和引用型別,ArrayList只能是引用型別
  2. Array固定大小,ArrayList長度可以動態改變
  3. ArrayList有很多方法可以呼叫,如addAll()

24. List和陣列的互相轉換/String轉換成陣列

  1. String[] a = list.toArray(new String[size]));
  2. List list = Arrays.asList(array);
  3. char[] char = string.toCharArray();

25. 陣列在記憶體中是如何分配的

和引用型別一樣,在棧中儲存一個引用,指向堆地址

26. TreeMap和TreeSet在排序時如何比較元素?Collections工具類中的sort()方法如何比較元素?

  1. TreeMap和TreeSet都是通過紅黑樹實現的,因此要求元素都是可比較的,元素必須實現Comparable介面,該介面中有compareTo()方法。
  2. Collections的sort方法有兩種過載形式,一種只有一個引數,要求傳入的待排序元素必須實現Comparable介面;第二種有兩個引數,要求傳入待排序容器即Comparator例項

27. 闡述ArrayList、Vector、LinkedList的儲存效能和特性。

  1. ArrayList 和Vector都是使用陣列方式儲存資料,陣列方式節省空間,便與讀取;但插入刪除涉及陣列移動,效能較差。
  2. Vector中的方法由於新增了synchronized修飾,因此Vector是執行緒安全的容器。
  3. LinkedList使用雙向連結串列實現儲存,佔用空間大,讀取慢;插入刪除快
  4. Vector已經被遺棄,不推薦使用。為了實現執行緒安全的list,可以使用Collections中的synchronizedList方法將其轉換成執行緒安全的容器後再使用

28. 什麼是Java優先順序佇列(Priority Queue)

PriorityQueue是一個基於優先順序堆的無界佇列,它的元素是按照自然順序(natural order)排序的。在建立的時候,我們可以給它提供一個負責給元素排序的比較器。PriorityQueue不允許null值,因為他們沒有自然順序,或者說他們沒有任何的相關聯的比較器。PriorityQueue的邏輯結構為堆(完全二叉樹),物理結構為陣列。最後,PriorityQueue不是執行緒安全的,入隊和出隊的時間複雜度是O(log(n))

29. List、Set、Map的遍歷方式

  1. List、Set都繼承了Collection介面,可以使用for each遍歷或者Iterator
  2. HashMap可以拿到KeySet或者entrySet,然後用iterator或者 for each 方式遍歷

30. 什麼是Iterator(迭代器)

迭代器是一個介面,我們可以藉助這個介面實現對集合的遍歷,刪除 擴充套件(迭代器實現原理):Collection繼承了Iterable介面,iterable介面中有iterator方法,返回一個Iterator迭代器 -> Collection的實現類通過在內部實現自定義Iterator,在iterator時返回這個例項。

31. Iterator和ListIterator的區別是什麼

  1. ListIterator 繼承自 Iterator
  2. Iterator可用來遍歷Set和List集合,但是ListIterator只能用來遍歷List
  3. ListIterator比Iterator增加了更多功能,例如可以雙向遍歷,增加元素等

32. 如何權衡是使用無序的陣列還是有序的陣列

  1. 查詢多用有序陣列,插入刪除多用無序陣列
  2. 解釋:有序陣列最大的好處在於查詢的時間複雜度是O(log n),而無序陣列是O(n)。有序陣列的缺點是插入操作的時間複雜度是O(n),因為值大的元素需要往後移動來給新元素騰位置。相反,無序陣列的插入時間複雜度是常量O(1)

33. Arrays.sort 實現原理和 Collections.sort 實現原理

  1. Collections.sort是通過Arrays.sort實現的。當list不為ArrayList時,先轉成陣列,再呼叫Arrays.sort
  2. java 8 中Array.Sort()是通過timsort(一種優化的歸併排序)來實現的

34. Collection和Collections的區別

  1. Collection 是一個介面,Set、List都繼承了該介面
  2. Collections 是一個工具類,該工具類可以幫我們完成對容器的判空,排序,執行緒安全化等。

35. 快速失敗(fail-fast)和安全失敗(fail-safe)

  1. 快速失敗:在用迭代器遍歷一個集合物件時,如果遍歷過程中對集合物件的內容進行了修改(增加、刪除、修改),則會丟擲Concurrent Modification Exception
  2. 安全失敗:操作是物件的副本,這個時候原物件改變並不會對當前迭代器遍歷產生影響。java.util.concurrent類下邊容器都是安全失敗
    擴充套件:快速失敗原理:容器內部有一個modCount,記錄變化的次數,當進行遍歷時,如果mocount值發生改變,責快速失敗

36. 當一個集合被作為引數傳遞給一個函式時,如何才可以確保函式不能修改它

在作為引數傳遞之前,我們可以使用Collections.unmodifiableCollection(Collection c)方法建立一個只讀集合,這將確保改變集合的任何操作都會丟擲UnsupportedOperationException。

記憶體


0. 記憶體中的棧(stack)、堆(heap)和方法區(method area)?

  1. 棧:執行緒獨有,每個執行緒一個棧區。儲存基本資料型別,物件的引用,函式呼叫的現場(棧可以分為三個部分:基本型別,執行環境上下文,操作指令區(存放操作指令));優點是速度快,缺點是大小和生存週期必須是確定的
  2. 堆:執行緒共享,jvm一共一個堆區。儲存物件的例項,垃圾回收器回收的是堆區的記憶體
  3. 方法區(靜態區):執行緒共享。儲存類資訊、常量、靜態變數、JIT編譯器編譯後的程式碼等資料,常量池是方法區的一部分。

1. Jvm記憶體模型

Android面試之Java中級篇

  1. 堆:執行緒共享,存放物件例項,所有的物件的記憶體都在這裡分配。垃圾回收主要就是作用於這裡的。
  2. java虛擬機器棧:執行緒私有,生命週期與執行緒相同。每個方法執行的時候都會建立一個棧幀(stack frame)用於存放 區域性變數表、操作棧、動態連結、方法出口
  3. native方法棧
  4. 程式計數器:這裡記錄了執行緒執行的位元組碼的行號,在分支、迴圈、跳轉、異常、執行緒恢復等都依賴這個計數器。
  5. 方法區:執行緒共享的儲存了每個類物件的資訊(包括類的名稱、方法資訊、欄位資訊)、靜態變數、常量以及編譯器編譯後的程式碼等。

垃圾回收


0. java中存在記憶體洩漏嗎

  1. java中存在記憶體洩漏
  2. java中雖然有GC幫我們自動回收記憶體,但是隻有當例項沒有引用指向它時才會被回收,若我們錯誤的持有了引用,沒有在應當釋放時釋放,就會造成記憶體洩漏,例如在長生命週期物件持有短生命週期物件。
舉例:
import java.util.Arrays;
import java.util.EmptyStackException;

public class MyStack<T> {
private T[] elements;
private int size = 0;

private static final int INIT_CAPACITY = 16;

public MyStack() {
   elements = (T[]) new Object[INIT_CAPACITY];
 }

public void push(T elem) {
   ensureCapacity();
   elements[size++] = elem;
 }

public T pop() {
if(size == 0)
   throw new EmptyStackException();
   return elements[--size];
   }

private void ensureCapacity() {
 if(elements.length == size) {
   elements = Arrays.copyOf(elements, 2 * size + 1);
  }
 }
}

分析:這裡用陣列實現了一個棧,但是當資料pop之後,陣列裡內容並沒有被清空。
複製程式碼

1. GC是什麼?為什麼要有GC?

  1. GC是垃圾收集器(Garbage Collection)的縮寫,是面試中常考的點。瞭解GC的執行方式,對防止記憶體洩漏,提高執行效率等都有好處
  2. 垃圾收集器會自動進行記憶體回收,不需要程式設計師進行操作,System.gc() 或Runtime.getRuntime().gc() 時並不是馬上進行記憶體回收,甚至不會進行記憶體回收
  3. 詳細參見JVM的記憶體回收機制

2. 如何定義垃圾

  1. 引用計數(無法解決迴圈引用的問題)
  2. 可達性分析

3. 什麼變數能作為GCRoot

  1. 虛擬機器棧(棧幀中的本地變數表)中引用的物件;
  2. 方法區中的類靜態屬性引用的物件
  3. 方法區中的常量引用的物件
  4. 原生方法棧(Native Method Stack)中 JNI 中引用的物件。

4. 垃圾回收的方法

  1. 標記-清除(Mark-Sweep)法:減少停頓時間,但會造成記憶體碎片
  2. 標記-整理(Mark-Compact)法:可以解決記憶體碎片問題,但是會增加停頓時間
  3. 複製(copying)法:從一個地方拷貝到另一個地方,適合有大量回收的場景,比如新生代回收
  4. 分代收集:把記憶體區域分成不同代,根據代不同採取不同的策略
    新生代(Yong Generation):存放新建立的物件,採用複製回收方法;年老代(old Generation):這些物件垃圾回收的頻率較低,採用的標記整理方法,這裡的垃圾回收叫做 major GC;永久代(permanent Generation):存放Java本身的一些資料,當類不再使用時,也會被回收。

5. JVM垃圾回收何時觸發MinorGC等操作

  1. minorGc發生在年輕代,是複製回收
  2. 年輕代可以分為三個區域:Eden、from Survivor和to Survivor;當Eden滿了的時候,觸發minorGc
  3. Gc過程:Eden區複製到to區;from區年齡大的被移到年老區,年齡小的複製到to區;to區變成from區;

6. JVM 年輕代到年老代的晉升過程的判斷條件是什麼

  1. 在年輕代gc過程中存活的次數超過閾值
  2. 或者太大了直接放入年老代
  3. to Survivor滿了,新物件直接放入老年代
  4. 還有一種情況,如果在From空間中,相同年齡所有物件的大小總和大於From和To空間總和的一半,那麼年齡大於等於該年齡的物件就會被移動到老年代,而不用等到15歲(預設)

7. Full GC 觸發的條件

  1. 呼叫System.gc時,系統建議執行Full GC,但是不必然執行
  2. 老年代或者永久代空間不足
  3. 其他(=-=)

8. OOM錯誤,stackoverflow錯誤,permgen space錯誤

  1. OOM 是堆記憶體溢位
  2. stackoverflow是棧記憶體溢位
  3. permgen space說的是溢位的區域在永久代

9. 記憶體溢位的種類

  1. stackoverflow:;當執行緒呼叫一個方法是,jvm壓入一個新的棧幀到這個執行緒的棧中,只要這個方法還沒返回,這個棧幀就存在。如果方法的巢狀呼叫層次太多(如遞迴呼叫),隨著java棧中的幀的增多,最終導致這個執行緒的棧中的所有棧幀的大小的總和大於-Xss設定的值,而產生生StackOverflowError溢位異常
  2. outofmemory:
    1. java程式啟動一個新執行緒時,沒有足夠的空間為改執行緒分配java棧,一個執行緒java棧的大小由-Xss設定決定;JVM則丟擲OutOfMemoryError異常;
    2. java堆用於存放物件的例項,當需要為物件的例項分配記憶體時,而堆的佔用已經達到了設定的最大值(通過-Xmx)設定最大值,則丟擲OutOfMemoryError異常;
    3. 方法區用於存放java類的相關資訊,如類名、訪問修飾符、常量池、欄位描述、方法描述等。在類載入器載入class檔案到記憶體中的時候,JVM會提取其中的類資訊,並將這些類資訊放到方法區中。當需要儲存這些類資訊,而方法區的記憶體佔用又已經達到最大值(通過-XX:MaxPermSize);將會丟擲OutOfMemoryError異常

參考資料

Java面試題全集(上)

http://www.jcodecraeer.com/a/chengxusheji/java/2015/0520/2896.html

相關文章