2019 Java常見面試題解析(一)

前言

簡述你所知道的Linux:Linux起源於1991年,1995年流行起來的免費操作系統,目前, Linux是主流的服務器操作系統, 廣泛應用於互聯網、雲計算、智能手機(Android)等領域。由於Java主要用於服務器端的開發,因此Java應用的部署環境有很多爲Linux。 
Windows操作系統的目錄結構,是以盤符爲單位,C盤、D盤、E盤等等,數據存儲在各個盤符之下,而Linux操作系統最頂層只有一個根目錄root,所有文件都存儲在這一個根目錄之下。 
Linux不像Windows的圖形操作界面,是通過命令的方式進行操作,常用命令有: 
a . pwd:用於顯示當前工作目錄; 
b . ls:用於查看當前工作目錄內容; 
c . cd:用於改變當前工作目錄。

什麼是Java虛擬機?爲什麼Java被稱作是“平臺無關的編程語言”?

Java虛擬機是一個可以執行Java字節碼的虛擬機進程。Java源文件被編譯成能被Java虛擬機執行的字節碼文件。 
Java被設計成允許應用程序可以運行在任意的平臺,而不需要程序員爲每一個平臺單獨重寫或者是重新編譯。Java虛擬機讓這個變爲可能,因爲它知道底層硬件平臺的指令長度和其他特性。
 

1. JDK、JRE、JVM關係是什麼?

JDK(Java Development Kit)即爲Java開發工具包,包含編寫Java程序所必須的編譯、運行等開發工具以及JRE。開發工具如:用於編譯java程序的javac命令、用於啓動JVM運行java程序的java命令、用於生成文檔的javadoc命令以及用於打包的jar命令等等。 
JRE(Java Runtime Environment)即爲Java運行環境,提供了運行Java應用程序所必須的軟件環境,包含有Java虛擬機(JVM)和豐富的系統類庫。系統類庫即爲java提前封裝好的功能類,只需拿來直接使用即可,可以大大的提高開發效率。 
JVM(Java Virtual Machines)即爲Java虛擬機,提供了字節碼文件(.class)的運行環境支持。 
簡單說,就是JDK包含JRE包含JVM。

2. == 和 equals 的區別是什麼?

== 的作用

  • 基本類型:比較的是值是否相同。
  • 引用類型:比較的是引用是否相同。

equals 的作用:比較的都是值是否相同。

代碼示例:

String x = "string";
String y = "string";
String z = new String("string");
System. out. println(x==y); // true
System. out. println(x==z); // false
System. out. println(x. equals(y)); // true
System. out. println(x. equals(z)); // true

代碼解讀:因爲 x 和 y 指向的是同一個引用,所以 == 也是 true,而 new String() 方法則重寫開闢了內存空間,所以 == 結果爲 false,而 equals 比較的一直是值,所以結果都爲 true。

3. 兩個對象的 hashCode() 相同,則 equals() 也一定爲 true,對嗎?

不對,兩個對象的 hashCode() 相同,equals() 不一定 true。

代碼示例:

String str1 = "通話";
String str2 = "重地";
System. out. println(String. format("str1:%d | str2:%d",  str1. hashCode(),str2. hashCode()));
System. out. println(str1. equals(str2));

執行的結果:

str1:1179395 | str2:1179395

false

代碼解讀:很顯然“通話”和“重地”的 hashCode() 相同,然而 equals() 則爲 false,因爲在散列表中,hashCode() 相等即兩個鍵值對的哈希值相等,然而哈希值相等,並不一定能得出鍵值對相等。

4. final 在 Java 中有什麼作用?

  • final 修飾的類叫最終類,該類不能被繼承。
  • final 修飾的方法不能被重寫。
  • final 修飾的變量叫常量,常量必須初始化,初始化之後值就不能被修改。

5. Java 中的 Math. round(-1. 5) 等於多少?

等於 -1,Math. round 四捨五入大於 0. 5 向上取整的。

6. String 屬於基礎的數據類型嗎?

String 不屬於基礎類型,基礎類型有 8 種:byte、boolear、char、short、int、float、long、double,String 屬於對象。

7. Java 中操作字符串都有哪些類?它們之間有什麼區別?

操作字符串的類有:String、StringBuffer、StringBuilder。

String 和 StringBuffer、StringBuilder 的區別在於 String 聲明的是不可變的對象,每次操作都會生成新的 String 對象,然後將指針指向新的 String 對象,而 StringBuffer、StringBuilder 可以在原有對象的基礎上進行操作,所以在經常改變字符串內容的情況下最好不要使用 String。

StringBuffer 和 StringBuilder 最大的區別在於,StringBuffer 是線程安全的,而 StringBuilder 是非線程安全的,但 StringBuilder 的性能卻高於 StringBuffer,所以在單線程環境下推薦使用 StringBuilder,多線程環境下推薦使用 StringBuffer。

8. String str="i"與 String str=new String("i")一樣嗎?

不一樣,因爲內存的分配方式不一樣。String str="i"的方式,Java 虛擬機會將其分配到常量池中;而 String str=new String("i") 則會被分到堆內存中。

9. 如何將字符串反轉?

使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。

示例代碼:

// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba

10. String 類的常用方法都有那些?

  • indexOf():返回指定字符的索引。
  • charAt():返回指定索引處的字符。
  • replace():字符串替換。
  • trim():去除字符串兩端空白。
  • split():分割字符串,返回一個分割後的字符串數組。
  • getBytes():返回字符串的 byte 類型數組。
  • length():返回字符串長度。
  • toLowerCase():將字符串轉成小寫字母。
  • toUpperCase():將字符串轉成大寫字符。
  • substring():截取字符串。
  • equals():字符串比較。

11. 抽象類必須要有抽象方法嗎?

不需要,抽象類不一定非要有抽象方法。

示例代碼:

abstract class Cat {
    public static void sayHi() {
        System. out. println("hi~");
    }
}

上面代碼,抽象類並沒有抽象方法但完全可以正常運行。

12. 普通類和抽象類有哪些區別?

  • 普通類不能包含抽象方法,抽象類可以包含抽象方法。
  • 抽象類不能直接實例化,普通類可以直接實例化。

13. 抽象類能使用 final 修飾嗎?

不能,定義抽象類就是讓其他類繼承的,如果定義爲 final 該類就不能被繼承,這樣彼此就會產生矛盾,所以 final 不能修飾抽象類,如下圖所示,編輯器也會提示錯誤信息:

!enter image description here

14. 接口和抽象類有什麼區別?

  • 默認方法實現:抽象類可以有默認的方法實現;接口不能有默認的方法實現。
  • 實現:抽象類的子類使用 extends 來繼承;接口必須使用 implements 來實現接口。
  • 構造函數:抽象類可以有構造函數;接口不能有。
  • main 方法:抽象類可以有 main 方法,並且我們能運行它;接口不能有 main 方法。
  • 實現數量:類可以實現很多個接口;但是隻能繼承一個抽象類。
  • 訪問修飾符:接口中的方法默認使用 public 修飾;抽象類中的方法可以是任意訪問修飾符。

15. Java 中 IO 流分爲幾種?

按功能來分:輸入流(input)、輸出流(output)。

按類型來分:字節流和字符流。

字節流和字符流的區別是:字節流按 8 位傳輸以字節爲單位輸入輸出數據,字符流按 16 位傳輸以字符爲單位輸入輸出數據。

16. BIO、NIO、AIO 有什麼區別?

  • BIO:Block IO 同步阻塞式 IO,就是我們平常使用的傳統 IO,它的特點是模式簡單使用方便,併發處理能力低。
  • NIO:New IO 同步非阻塞 IO,是傳統 IO 的升級,客戶端和服務器端通過 Channel(通道)通訊,實現了多路複用。
  • AIO:Asynchronous IO 是 NIO 的升級,也叫 NIO2,實現了異步非堵塞 IO ,異步 IO 的操作基於事件和回調機制。

17. Files的常用方法都有哪些?

  • Files. exists():檢測文件路徑是否存在。
  • Files. createFile():創建文件。
  • Files. createDirectory():創建文件夾。
  • Files. delete():刪除一個文件或目錄。
  • Files. copy():複製文件。
  • Files. move():移動文件。
  • Files. size():查看文件個數。
  • Files. read():讀取文件。
  • Files. write():寫入文件。

容器

18. Java 容器都有哪些?

Java 容器分爲 Collection 和 Map 兩大類,其下又有很多子類,如下所示:

  • Collection
  • List
    • ArrayList
    • LinkedList
    • Vector
    • Stack
  • Set
    • HashSet
    • LinkedHashSet
    • TreeSet
  • Map
  • HashMap
    • LindedHashMap
  • TreeMap
  • ConcurrentHashMap
  • Hashtable

19. Collection 和 Collections 有什麼區別?

  • Collection 是一個集合接口,它提供了對集合對象進行基本操作的通用接口方法,所有集合都是它的子類,比如 List、Set 等。
  • Collections 是一個包裝類,包含了很多靜態方法,不能被實例化,就像一個工具類,比如提供的排序方法: Collections. sort(list)。

20. List、Set、Map 之間的區別是什麼?

List、Set、Map 的區別主要體現在兩個方面:元素是否有序、是否允許元素重複。

三者之間的區別,如下表:

!enter image description here

21. HashMap 和 Hashtable 有什麼區別?

  • 存儲:HashMap 運行 key 和 value 爲 null,而 Hashtable 不允許。
  • 線程安全:Hashtable 是線程安全的,而 HashMap 是非線程安全的。
  • 推薦使用:在 Hashtable 的類註釋可以看到,Hashtable 是保留類不建議使用,推薦在單線程環境下使用 HashMap 替代,如果需要多線程使用則用 ConcurrentHashMap 替代。

22. 如何決定使用 HashMap 還是 TreeMap?

對於在 Map 中插入、刪除、定位一個元素這類操作,HashMap 是最好的選擇,因爲相對而言 HashMap 的插入會更快,但如果你要對一個 key 集合進行有序的遍歷,那 TreeMap 是更好的選擇。

23. 說一下 HashMap 的實現原理?

HashMap 基於 Hash 算法實現的,我們通過 put(key,value)存儲,get(key)來獲取。當傳入 key 時,HashMap 會根據 key. hashCode() 計算出 hash 值,根據 hash 值將 value 保存在 bucket 裏。當計算出的 hash 值相同時,我們稱之爲 hash 衝突,HashMap 的做法是用鏈表和紅黑樹存儲相同 hash 值的 value。當 hash 衝突的個數比較少時,使用鏈表否則使用紅黑樹。

24. 說一下 HashSet 的實現原理?

HashSet 是基於 HashMap 實現的,HashSet 底層使用 HashMap 來保存所有元素,因此 HashSet 的實現比較簡單,相關 HashSet 的操作,基本上都是直接調用底層 HashMap 的相關方法來完成,HashSet 不允許重複的值。

25. ArrayList 和 LinkedList 的區別是什麼?

  • 數據結構實現:ArrayList 是動態數組的數據結構實現,而 LinkedList 是雙向鏈表的數據結構實現。
  • 隨機訪問效率:ArrayList 比 LinkedList 在隨機訪問的時候效率要高,因爲 LinkedList 是線性的數據存儲方式,所以需要移動指針從前往後依次查找。
  • 增加和刪除效率:在非首尾的增加和刪除操作,LinkedList 要比 ArrayList 效率要高,因爲 ArrayList 增刪操作要影響數組內的其他數據的下標。

綜合來說,在需要頻繁讀取集合中的元素時,更推薦使用 ArrayList,而在插入和刪除操作較多時,更推薦使用 LinkedList。

26. 如何實現數組和 List 之間的轉換?

  • 數組轉 List:使用 Arrays. asList(array) 進行轉換。
  • List 轉數組:使用 List 自帶的 toArray() 方法。

代碼示例:

// list to array
List<String> list = new ArrayList<String>();
list. add("王磊");
list. add("的博客");
list. toArray();
// array to list
String[] array = new String[]{"王磊","的博客"};
Arrays. asList(array);

27. ArrayList 和 Vector 的區別是什麼?

  • 線程安全:Vector 使用了 Synchronized 來實現線程同步,是線程安全的,而 ArrayList 是非線程安全的。
  • 性能:ArrayList 在性能方面要優於 Vector。
  • 擴容:ArrayList 和 Vector 都會根據實際的需要動態的調整容量,只不過在 Vector 擴容每次會增加 1 倍,而 ArrayList 只會增加 50%。

28. Array 和 ArrayList 有何區別?

  • Array 可以存儲基本數據類型和對象,ArrayList 只能存儲對象。
  • Array 是指定固定大小的,而 ArrayList 大小是自動擴展的。
  • Array 內置方法沒有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。

29. 在 Queue 中 poll()和 remove()有什麼區別?

  • 相同點:都是返回第一個元素,並在隊列中刪除返回的對象。
  • 不同點:如果沒有元素 poll()會返回 null,而 remove()會直接拋出 NoSuchElementException 異常。

代碼示例:

Queue<String> queue = new LinkedList<String>();
queue. offer("string"); // add
System. out. println(queue. poll());
System. out. println(queue. remove());
System. out. println(queue. size());

30. 哪些集合類是線程安全的?

Vector、Hashtable、Stack 都是線程安全的,而像 HashMap 則是非線程安全的,不過在 JDK 1.5 之後隨着 Java. util. concurrent 併發包的出現,它們也有了自己對應的線程安全類,比如 HashMap 對應的線程安全類就是 ConcurrentHashMap。

31. 迭代器 Iterator 是什麼?

Iterator 接口提供遍歷任何 Collection 的接口。我們可以從一個 Collection 中使用迭代器方法來獲取迭代器實例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允許調用者在迭代過程中移除元素。

32. Iterator 怎麼使用?有什麼特點?

Iterator 使用代碼如下:

List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
  String obj = it. next();
  System. out. println(obj);
}

Iterator 的特點是更加安全,因爲它可以確保,在當前遍歷的集合元素被更改的時候,就會拋出 ConcurrentModificationException 異常。

33. Iterator 和 ListIterator 有什麼區別?

  • Iterator 可以遍歷 Set 和 List 集合,而 ListIterator 只能遍歷 List。
  • Iterator 只能單向遍歷,而 ListIterator 可以雙向遍歷(向前/後遍歷)。
  • ListIterator 從 Iterator 接口繼承,然後添加了一些額外的功能,比如添加一個元素、替換一個元素、獲取前面或後面元素的索引位置。

34. 怎麼確保一個集合不能被修改?

可以使用 Collections. unmodifiableCollection(Collection c) 方法來創建一個只讀集合,這樣改變集合的任何操作都會拋出 Java. lang. UnsupportedOperationException 異常。

示例代碼如下:

List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 運行時此行報錯
System. out. println(list. size());

多線程

35. 並行和併發有什麼區別?

  • 並行:一個處理器同時處理多個任務。
  • 併發:多個處理器或多核處理器同時處理多個不同的任務。

36. 線程和進程的區別?

一個程序下至少有一個進程,一個進程下至少有一個線程,一個進程下也可以有多個線程來增加程序的執行速度。

37. 守護線程是什麼?

守護線程是運行在後臺的一種特殊進程。它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。在 Java 中垃圾回收線程就是特殊的守護線程。

38. 創建線程有哪幾種方式?

創建線程有三種方式:

  • 繼承 Thread 重新 run 方法;
  • 實現 Runnable 接口;
  • 實現 Callable 接口。

39. 說一下 runnable 和 callable 有什麼區別?

runnable 沒有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的補充。

40. 線程有哪些狀態?

線程的狀態:

  • NEW 尚未啓動
  • RUNNABLE 正在執行中
  • BLOCKED 阻塞的(被同步鎖或者IO鎖阻塞)
  • WAITING 永久等待狀態
  • TIMED_WAITING 等待指定的時間重新被喚醒的狀態
  • TERMINATED 執行完成

41. sleep() 和 wait() 有什麼區別?

  • 類的不同:sleep() 來自 Thread,wait() 來自 Object。
  • 釋放鎖:sleep() 不釋放鎖;wait() 釋放鎖。
  • 用法不同:sleep() 時間到會自動恢復;wait() 可以使用 notify()/notifyAll()直接喚醒。

42. notify()和 notifyAll()有什麼區別?

notifyAll()會喚醒所有的線程,notify()之後喚醒一個線程。notifyAll() 調用後,會將全部線程由等待池移到鎖池,然後參與鎖的競爭,競爭成功則繼續執行,如果不成功則留在鎖池等待鎖被釋放後再次參與競爭。而 notify()只會喚醒一個線程,具體喚醒哪一個線程由虛擬機控制。

43. 線程的 run() 和 start() 有什麼區別?

start() 方法用於啓動線程,run() 方法用於執行線程的運行時代碼。run() 可以重複調用,而 start() 只能調用一次。

44. 創建線程池有哪幾種方式?

線程池創建有七種方式,最核心的是最後一種:

  • newSingleThreadExecutor():它的特點在於工作線程數目被限制爲 1,操作一個無界的工作隊列,所以它保證了所有任務的都是被順序執行,最多會有一個任務處於活動狀態,並且不允許使用者改動線程池實例,因此可以避免其改變線程數目;

  • newCachedThreadPool():它是一種用來處理大量短時間工作任務的線程池,具有幾個鮮明特點:它會試圖緩存線程並重用,當無緩存線程可用時,就會創建新的工作線程;如果線程閒置的時間超過 60 秒,則被終止並移出緩存;長時間閒置時,這種線程池,不會消耗什麼資源。其內部使用 SynchronousQueue 作爲工作隊列;

  • newFixedThreadPool(int nThreads):重用指定數目(nThreads)的線程,其背後使用的是無界的工作隊列,任何時候最多有 nThreads 個工作線程是活動的。這意味着,如果任務數量超過了活動隊列數目,將在工作隊列中等待空閒線程出現;如果有工作線程退出,將會有新的工作線程被創建,以補足指定的數目 nThreads;

  • newSingleThreadScheduledExecutor():創建單線程池,返回 ScheduledExecutorService,可以進行定時或週期性的工作調度;

  • newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()類似,創建的是個 ScheduledExecutorService,可以進行定時或週期性的工作調度,區別在於單一工作線程還是多個工作線程;

  • newWorkStealingPool(int parallelism):這是一個經常被人忽略的線程池,Java 8 才加入這個創建方法,其內部會構建ForkJoinPool,利用Work-Stealing算法,並行地處理任務,不保證處理順序;

  • ThreadPoolExecutor():是最原始的線程池創建,上面1-3創建方式都是對ThreadPoolExecutor的封裝。

45. 線程池都有哪些狀態?

  • RUNNING:這是最正常的狀態,接受新的任務,處理等待隊列中的任務。
  • SHUTDOWN:不接受新的任務提交,但是會繼續處理等待隊列中的任務。
  • STOP:不接受新的任務提交,不再處理等待隊列中的任務,中斷正在執行任務的線程。
  • TIDYING:所有的任務都銷燬了,workCount 爲 0,線程池的狀態在轉換爲 TIDYING 狀態時,會執行鉤子方法 terminated()。
  • TERMINATED:terminated()方法結束後,線程池的狀態就會變成這個。

46. 線程池中 submit() 和 execute() 方法有什麼區別?

  • execute():只能執行 Runnable 類型的任務。
  • submit():可以執行 Runnable 和 Callable 類型的任務。

Callable 類型的任務可以獲取執行的返回值,而 Runnable 執行無返回值。

47. 在 Java 程序中怎麼保證多線程的運行安全?

  • 方法一:使用安全類,比如 Java. util. concurrent 下的類。
  • 方法二:使用自動鎖 synchronized。
  • 方法三:使用手動鎖 Lock。

手動鎖 Java 示例代碼如下:

Lock lock = new ReentrantLock();
lock. lock();
try {
    System. out. println("獲得鎖");
} catch (Exception e) {
    // TODO: handle exception
} finally {
    System. out. println("釋放鎖");
    lock. unlock();
}

48. 多線程鎖的升級原理是什麼?

在鎖對象的對象頭裏面有一個 threadid 字段,在第一次訪問的時候 threadid 爲空,JVM 讓其持有偏向鎖,並將threadid 設置爲其線程 id,再次進入的時候會先判斷 threadid 是否尤其線程 id 一致,如果一致則可以直接使用,如果不一致,則升級偏向鎖爲輕量級鎖,通過自旋循環一定次數來獲取鎖,不會堵塞,執行一定次數之後就會升級爲重量級鎖,進入堵塞,整個過程就是鎖升級的原理。

鎖的升級的目的:在 Java 6 之後優化 synchronized 的實現方式,使用了偏向鎖升級爲輕量級鎖再升級到重量級鎖的方式,減低了鎖帶來的性能消耗。

鎖升級是爲了減低了鎖帶來的性能消耗。

49. 什麼是死鎖?

當線程 A 持有獨佔鎖a,並嘗試去獲取獨佔鎖 b 的同時,線程 B 持有獨佔鎖 b,並嘗試獲取獨佔鎖 a 的情況下,就會發生 AB 兩個線程由於互相持有對方需要的鎖,而發生的阻塞現象,我們稱爲死鎖。

50. 怎麼防止死鎖?

  • 儘量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),設置超時時間,超時可以退出防止死鎖。
  • 儘量使用 Java. util. concurrent 併發類代替自己手寫鎖。
  • 儘量降低鎖的使用粒度,儘量不要幾個功能用同一把鎖。
  • 儘量減少同步的代碼塊。

51. ThreadLocal 是什麼?有哪些使用場景?

ThreadLocal 爲每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。

ThreadLocal 的經典使用場景是數據庫連接和 session 管理等。

52. 說一下 synchronized 底層實現原理?

synchronized 是由一對 monitorenter/monitorexit 指令實現的,monitor 對象是同步的基本實現單元。在 Java 6 之前,monitor 的實現完全是依靠操作系統內部的互斥鎖,因爲需要進行用戶態到內核態的切換,所以同步操作是一個無差別的重量級操作,性能也很低。但在 Java 6 的時候,Java 虛擬機 對此進行了大刀闊斧地改進,提供了三種不同的 monitor 實現,也就是常說的三種不同的鎖:偏向鎖(Biased Locking)、輕量級鎖和重量級鎖,大大改進了其性能。

53. synchronized 和 volatile 的區別是什麼?

  • volatile 是變量修飾符;synchronized 是修飾類、方法、代碼段。
  • volatile 僅能實現變量的修改可見性,不能保證原子性;而 synchronized 則可以保證變量的修改可見性和原子性。
  • volatile 不會造成線程的阻塞;synchronized 可能會造成線程的阻塞。

54. synchronized 和 Lock 有什麼區別?

  • synchronized 可以給類、方法、代碼塊加鎖;而 lock 只能給代碼塊加鎖。
  • synchronized 不需要手動獲取鎖和釋放鎖,使用簡單,發生異常會自動釋放鎖,不會造成死鎖;而 lock 需要自己加鎖和釋放鎖,如果使用不當沒有 unLock()去釋放鎖就會造成死鎖。
  • 通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。

55. synchronized 和 ReentrantLock 區別是什麼?

synchronized 早期的實現比較低效,對比 ReentrantLock,大多數場景性能都相差較大,但是在 Java 6 中對 synchronized 進行了非常多的改進。

主要區別如下:

  • ReentrantLock 使用起來比較靈活,但是必須有釋放鎖的配合動作;
  • ReentrantLock 必須手動獲取與釋放鎖,而 synchronized 不需要手動釋放和開啓鎖;
  • ReentrantLock 只適用於代碼塊鎖,而 synchronized 可用於修飾方法、代碼塊等。
  • volatile 標記的變量不會被編譯器優化;synchronized 標記的變量可以被編譯器優化。

56. 說一下 atomic 的原理?

atomic 主要利用 CAS (Compare And Wwap) 和 volatile 和 native 方法來保證原子操作,從而避免 synchronized 的高開銷,執行效率大爲提升。

反射

57. 什麼是反射?

反射是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲 Java 語言的反射機制。

58. 什麼是 Java 序列化?什麼情況下需要序列化?

Java 序列化是爲了保存各種對象在內存中的狀態,並且可以把保存的對象狀態再讀出來。

以下情況需要使用 Java 序列化:

  • 想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;
  • 想用套接字在網絡上傳送對象的時候;
  • 想通過RMI(遠程方法調用)傳輸對象的時候。

59. 動態代理是什麼?有哪些應用?

動態代理是運行時動態生成代理類。

動態代理的應用有 spring aop、hibernate 數據查詢、測試框架的後端 mock、rpc,Java註解對象獲取等。

60. 怎麼實現動態代理?

JDK 原生動態代理和 cglib 動態代理。JDK 原生動態代理是基於接口實現的,而 cglib 是基於繼承當前類的子類實現的。

對象拷貝

61. 爲什麼要使用克隆?

克隆的對象可能包含一些已經修改過的屬性,而 new 出來的對象的屬性都還是初始化時候的值,所以當需要一個新的對象來保存當前對象的“狀態”就靠克隆方法了。

62. 如何實現對象克隆?

  • 實現 Cloneable 接口並重寫 Object 類中的 clone() 方法。
  • 實現 Serializable 接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深度克隆。

63. 深拷貝和淺拷貝區別是什麼?

  • 淺克隆:當對象被複制時只複製它本身和其中包含的值類型的成員變量,而引用類型的成員對象並沒有複製。
  • 深克隆:除了對象本身被複制外,對象所包含的所有成員變量也將複製。

Java Web

64. JSP 和 servlet 有什麼區別?

JSP 是 servlet 技術的擴展,本質上就是 servlet 的簡易方式。servlet 和 JSP 最主要的不同點在於,servlet 的應用邏輯是在 Java 文件中,並且完全從表示層中的 html 裏分離開來,而 JSP 的情況是 Java 和 html 可以組合成一個擴展名爲 JSP 的文件。JSP 側重於視圖,servlet 主要用於控制邏輯。

65. JSP 有哪些內置對象?作用分別是什麼?

JSP 有 9 大內置對象:

  • request:封裝客戶端的請求,其中包含來自 get 或 post 請求的參數;
  • response:封裝服務器對客戶端的響應;
  • pageContext:通過該對象可以獲取其他對象;
  • session:封裝用戶會話的對象;
  • application:封裝服務器運行環境的對象;
  • out:輸出服務器響應的輸出流對象;
  • config:Web 應用的配置對象;
  • page:JSP 頁面本身(相當於 Java 程序中的 this);
  • exception:封裝頁面拋出異常的對象。

66. 說一下 JSP 的 4 種作用域?

  • page:代表與一個頁面相關的對象和屬性。
  • request:代表與客戶端發出的一個請求相關的對象和屬性。一個請求可能跨越多個頁面,涉及多個 Web 組件;需要在頁面顯示的臨時數據可以置於此作用域。
  • session:代表與某個用戶與服務器建立的一次會話相關的對象和屬性。跟某個用戶相關的數據應該放在用戶自己的 session 中。
  • application:代表與整個 Web 應用程序相關的對象和屬性,它實質上是跨越整個 Web 應用程序,包括多個頁面、請求和會話的一個全局作用域。

67. session 和 cookie 有什麼區別?

  • 存儲位置不同:session 存儲在服務器端;cookie 存儲在瀏覽器端。
  • 安全性不同:cookie 安全性一般,在瀏覽器存儲,可以被僞造和修改。
  • 容量和個數限制:cookie 有容量限制,每個站點下的 cookie 也有個數限制。
  • 存儲的多樣性:session 可以存儲在 Redis 中、數據庫中、應用程序中;而 cookie 只能存儲在瀏覽器中。

68. 說一下 session 的工作原理?

session 的工作原理是客戶端登錄完成之後,服務器會創建對應的 session,session 創建完之後,會把 session 的 id 發送給客戶端,客戶端再存儲到瀏覽器中。這樣客戶端每次訪問服務器時,都會帶着 sessionid,服務器拿到 sessionid 之後,在內存找到與之對應的 session 這樣就可以正常工作了。

69. 如果客戶端禁止 cookie 能實現 session 還能用嗎?

可以用,session 只是依賴 cookie 存儲 sessionid,如果 cookie 被禁用了,可以使用 url 中添加 sessionid 的方式保證 session 能正常使用。

70. spring mvc 和 struts 的區別是什麼?

  • 攔截級別:struts2 是類級別的攔截;spring mvc 是方法級別的攔截。
  • 數據獨立性:spring mvc 的方法之間基本上獨立的,獨享 request 和 response 數據,請求數據通過參數獲取,處理結果通過 ModelMap 交回給框架,方法之間不共享變量;而 struts2 雖然方法之間也是獨立的,但其所有 action 變量是共享的,這不會影響程序運行,卻給我們編碼和讀程序時帶來了一定的麻煩。
  • 攔截機制:struts2 有以自己的 interceptor 機制,spring mvc 用的是獨立的 aop 方式,這樣導致struts2 的配置文件量比 spring mvc 大。
  • 對 ajax 的支持:spring mvc 集成了ajax,所有 ajax 使用很方便,只需要一個註解 @ResponseBody 就可以實現了;而 struts2 一般需要安裝插件或者自己寫代碼纔行。

71. 如何避免 SQL 注入?

  • 使用預處理 PreparedStatement。
  • 使用正則表達式過濾掉字符中的特殊字符。

72. 什麼是 XSS 攻擊,如何避免?

XSS 攻擊:即跨站腳本攻擊,它是 Web 程序中常見的漏洞。原理是攻擊者往 Web 頁面裏插入惡意的腳本代碼(css 代碼、Javascript 代碼等),當用戶瀏覽該頁面時,嵌入其中的腳本代碼會被執行,從而達到惡意攻擊用戶的目的,如盜取用戶 cookie、破壞頁面結構、重定向到其他網站等。

預防 XSS 的核心是必須對輸入的數據做過濾處理。

73. 什麼是 CSRF 攻擊,如何避免?

CSRF:Cross-Site Request Forgery(中文:跨站請求僞造),可以理解爲攻擊者盜用了你的身份,以你的名義發送惡意請求,比如:以你名義發送郵件、發消息、購買商品,虛擬貨幣轉賬等。

防禦手段:

  • 驗證請求來源地址;
  • 關鍵操作添加驗證碼;
  • 在請求地址添加 token 並驗證。

異常

74. throw 和 throws 的區別?

  • throw:是真實拋出一個異常。
  • throws:是聲明可能會拋出一個異常。

75. final、finally、finalize 有什麼區別?

  • final:是修飾符,如果修飾類,此類不能被繼承;如果修飾方法和變量,則表示此方法和此變量不能在被改變,只能使用。
  • finally:是 try{} catch{} finally{} 最後一部分,表示不論發生任何情況都會執行,finally 部分可以省略,但如果 finally 部分存在,則一定會執行 finally 裏面的代碼。
  • finalize: 是 Object 類的一個方法,在垃圾收集器執行的時候會調用被回收對象的此方法。

76. try-catch-finally 中哪個部分可以省略?

try-catch-finally 其中 catch 和 finally 都可以被省略,但是不能同時省略,也就是說有 try 的時候,必須後面跟一個 catch 或者 finally。

77. try-catch-finally 中,如果 catch 中 return 了,finally 還會執行嗎?

finally 一定會執行,即使是 catch 中 return 了,catch 中的 return 會等 finally 中的代碼執行完之後,纔會執行。

78. 常見的異常類有哪些?

  • NullPointerException 空指針異常
  • ClassNotFoundException 指定類不存在
  • NumberFormatException 字符串轉換爲數字異常
  • IndexOutOfBoundsException 數組下標越界異常
  • ClassCastException 數據類型轉換異常
  • FileNotFoundException 文件未找到異常
  • NoSuchMethodException 方法不存在異常
  • IOException IO 異常
  • SocketException Socket 異常

網絡

79. http 響應碼 301 和 302 代表的是什麼?有什麼區別?

301:永久重定向。

302:暫時重定向。

它們的區別是,301 對搜索引擎優化(SEO)更加有利;302 有被提示爲網絡攔截的風險。

80. forward 和 redirect 的區別?

forward 是轉發 和 redirect 是重定向:

  • 地址欄 url 顯示:foward url 不會發生改變,redirect url 會發生改變;
  • 數據共享:forward 可以共享 request 裏的數據,redirect 不能共享;
  • 效率:forward 比 redirect 效率高。

81. 簡述 tcp 和 udp的區別?

tcp 和 udp 是 OSI 模型中的運輸層中的協議。tcp 提供可靠的通信傳輸,而 udp 則常被用於讓廣播和細節控制交給應用的通信傳輸。

兩者的區別大致如下:

  • tcp 面向連接,udp 面向非連接即發送數據前不需要建立鏈接;
  • tcp 提供可靠的服務(數據傳輸),udp 無法保證;
  • tcp 面向字節流,udp 面向報文;
  • tcp 數據傳輸慢,udp 數據傳輸快;

82. tcp 爲什麼要三次握手,兩次不行嗎?爲什麼?

如果採用兩次握手,那麼只要服務器發出確認數據包就會建立連接,但由於客戶端此時並未響應服務器端的請求,那此時服務器端就會一直在等待客戶端,這樣服務器端就白白浪費了一定的資源。若採用三次握手,服務器端沒有收到來自客戶端的再此確認,則就會知道客戶端並沒有要求建立請求,就不會浪費服務器的資源。

83. 說一下 tcp 粘包是怎麼產生的?

tcp 粘包可能發生在發送端或者接收端,分別來看兩端各種產生粘包的原因:

  • 發送端粘包:發送端需要等緩衝區滿才發送出去,造成粘包;
  • 接收方粘包:接收方不及時接收緩衝區的包,造成多個包接收。

84. OSI 的七層模型都有哪些?

  • 物理層:利用傳輸介質爲數據鏈路層提供物理連接,實現比特流的透明傳輸。
  • 數據鏈路層:負責建立和管理節點間的鏈路。
  • 網絡層:通過路由選擇算法,爲報文或分組通過通信子網選擇最適當的路徑。
  • 傳輸層:向用戶提供可靠的端到端的差錯和流量控制,保證報文的正確傳輸。
  • 會話層:向兩個實體的表示層提供建立和使用連接的方法。
  • 表示層:處理用戶信息的表示問題,如編碼、數據格式轉換和加密解密等。
  • 應用層:直接向用戶提供服務,完成用戶希望在網絡上完成的各種工作。

85. get 和 post 請求有哪些區別?

  • get 請求會被瀏覽器主動緩存,而 post 不會。
  • GET使用URL或Cookie傳參,而POST將數據放在BODY中。
  • get 傳遞參數有大小限制,而 post 沒有。
  • post 參數傳輸更安全,get 的參數會明文限制在 url 上,post 不會。

說到get 和 post 請求有哪些區別,大多數同學都會想到這幾個標準答案,但是這三個答案是否是正確的還是有待商榷

1. GET使用URL或Cookie傳參,而POST將數據放在BODY中

GET和POST是由HTTP協議定義的。在HTTP協議中,Method和Data(URL, Body, Header)是正交的兩個概念,也就是說,使用哪個Method與應用層的數據如何傳輸是沒有相互關係的。

HTTP沒有要求,如果Method是POST數據就要放在BODY中。也沒有要求,如果Method是GET,數據(參數)就一定要放在URL中而不能放在BODY中。

那麼,網上流傳甚廣的這個說法是從何而來的呢?我在HTML標準中,找到了相似的描述。這和網上流傳的說法一致。但是這只是HTML標準對HTTP協議的用法的約定。怎麼能當成GET和POST的區別呢?

而且,現代的Web Server都是支持GET中包含BODY這樣的請求。雖然這種請求不可能從瀏覽器發出,但是現在的Web Server又不是隻給瀏覽器用,已經完全地超出了HTML服務器的範疇了。

2. GET方式提交的數據有長度限制,則POST的數據則可以非常大

先說結論:HTTP協議對GET和POST都沒有對長度的限制。HTTP協議明確地指出了,HTTP頭和Body都沒有長度的要求。

首先是"GET方式提交的數據有長度限制",如果我們使用GET通過URL提交數據,那麼GET可提交的數據量就跟URL的長度有直接關係了。而實際上,URL不存在參數上限的問題,HTTP協議規範沒有對URL長度進行限制。這個限制是特定的瀏覽器及服務器對它的限制。IE對URL長度的限制是2083字節(2K+35)。對於其他瀏覽器,如Netscape、FireFox等,理論上沒有長度限制,其限制取決於操作系統的支持。

注意這個限制是整個URL長度,而不僅僅是你的參數值數據長度。

POST也是一樣,POST是沒有大小限制的,HTTP協議規範也沒有對POST數據進行大小限制,起限制作用的是服務器的處理程序的處理能力。

當然,我們常說GET的URL會有長度上的限制這個說法是怎麼回事呢?雖然這個不是GET和POST的本質區別,但是我們也可以說說導致URL長度限制的兩方面的原因:

1. 瀏覽器。早期的瀏覽器會對URL長度做限制。而現在的具體限制是怎麼樣的,我自己沒有親測過,就不復制網上的說法啦。

2. 服務器。URL長了,對服務器處理也是一種負擔。原本一個會話就沒有多少數據,現在如果有人惡意地構造幾個M大小的URL,並不停地訪問你的服務器。服務器的最大併發數顯然會下降。另一種攻擊方式是,告訴服務器Content-Length是一個很大的數,然後只給服務器發一點兒數據,服務器你就傻等着去吧。哪怕你有超時設置,這種故意的次次訪問超時也能讓服務器吃不了兜着走。有鑑於此,多數服務器出於安全啦、穩定啦方面的考慮,會給URL長度加限制。但是這個限制是針對所有HTTP請求的,與GET、POST沒有關係。

3. POST比GET安全,因爲數據在地址欄上不可見

這個說法其實也是基於上面的1,2兩點的基礎上來說的,我覺得沒什麼問題,但是需要明白爲什麼使用GET在地址欄上就不安全了,以及還有沒有其他原因說明“POST比GET安全”。

通過GET提交數據,用戶名和密碼將明文出現在URL上,因爲登錄頁面有可能被瀏覽器緩存,其他人查看瀏覽器的歷史紀錄,那麼別人就可以拿到你的賬號和密碼了,除此之外,使用GET提交數據還可能會造成Cross-site request forgery攻擊。

三、我的理解

“1. GET使用URL或Cookie傳參,而POST將數據放在BODY中”,這個是因爲HTTP協議用法的約定。並非它們的本身區別。

“2. GET方式提交的數據有長度限制,則POST的數據則可以非常大”,這個是因爲它們使用的操作系統和瀏覽器設置的不同引起的區別。也不是GET和POST本身的區別。

“3. POST比GET安全,因爲數據在地址欄上不可見”,這個說法沒毛病,但依然不是GET和POST本身的區別。

雖然這三點不是它們的本身區別,但至少是它們在使用上的區別,所以我在面試這個問題時,如果面試者能夠回答上面三點我基本會給個及格分。那麼你想不想要更高的分數?

四、終極區別

GET和POST最大的區別主要是GET請求是冪等性的,POST請求不是。這個是它們本質區別,上面的只是在使用上的區別。

什麼是冪等性?冪等性是指一次和多次請求某一個資源應該具有同樣的副作用。簡單來說意味着對同一URL的多個請求應該返回同樣的結果。

關於冪等性看我評論上推薦的一篇文章。

正因爲它們有這樣的區別,所以不應該且不能用get請求做數據的增刪改這些有副作用的操作。因爲get請求是冪等的,在網絡不好的隧道中會嘗試重試。如果用get請求增數據,會有重複操作的風險,而這種重複操作可能會導致副作用(瀏覽器和操作系統並不知道你會用get請求去做增操作)。

五、我的建議

如果面試官問你這個問題時,我建議你說出上面三點,同時要說明那三點是它們在使用上的區別,當然也要把它們的終極區別給說出來。

PS:曾經有一個研讀了HTTP協議的人去一家公司面試,面試官問他這個問題時,他回答“GET是用於獲取數據的,POST一般用於將數據發給服務器。其他GET和POST沒什麼區別”,於是被刷了。

因爲有些面試官心中也只有那一個“標準答案”。

感謝舊夢呢同學對這一問題指正!

86. 如何實現跨域?

實現跨域有以下幾種方案:

  • 服務器端運行跨域 設置 CORS 等於 *;
  • 在單個接口使用註解 @CrossOrigin 運行跨域;
  • 使用 jsonp 跨域;

未完待續!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章