Android面試之Java中級篇

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

  • 集合
  • 內存
  • 垃圾回收

集合


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是線性結構的容器,適用於按數值索引訪問元素的情形java

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))bash

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。jvm

內存


0. 內存中的棧(stack)、堆(heap)和方法區(method area)?

  1. 棧:線程獨有,每一個線程一個棧區。保存基本數據類型,對象的引用,函數調用的現場(棧能夠分爲三個部分:基本類型,執行環境上下文,操做指令區(存放操做指令));優勢是速度快,缺點是大小和生存週期必須是肯定的
  2. 堆:線程共享,jvm一共一個堆區。保存對象的實例,垃圾回收器回收的是堆區的內存
  3. 方法區(靜態區):線程共享。保存類信息、常量、靜態變量、JIT編譯器編譯後的代碼等數據,常量池是方法區的一部分。

1. Jvm內存模型

  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內存模型

  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

相關文章
相關標籤/搜索