2020最新Java面試題,常見面試題及答案彙總

發現網上很多Java面試題都沒有答案,所以花了很長時間蒐集整理出來了這套Java面試題大全,希望對大家有幫助哈~

一、Java 基礎

1. JDK 和 JRE 有什麼區別?

  • JDK:Java Development Kit 的簡稱,java 開發工具包,提供了 java 的開發環境和運行環境。
  • JRE:Java Runtime Environment 的簡稱,java 運行環境,為 java 的運行提供了所需環境。

具體來說 JDK 其實包含了 JRE,同時還包含了編譯 java 源碼的編譯器 javac,還包含了很多 java 程序調試和分析的工具。簡單來說:如果你需要運行 java 程序,只需安裝 JRE 就可以了,如果你需要編寫 java 程序,需要安裝 JDK。

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

== 解讀

對於基本類型和引用類型 == 的作用效果是不同的,如下所示:

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

代碼示例:

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

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

equals 解讀

equals 本質上就是 ==,只不過 String 和 Integer 等重寫了 equals 方法,把它變成了值比較。看下面的代碼就明白了。

首先來看默認情況下 equals 比較一個有相同值的對象,代碼如下:

class Cat {    public Cat(String name) {        this.name = name;    }     private String name;     public String getName() {        return name;    }     public void setName(String name) {        this.name = name;    }} Cat c1 = new Cat("王磊");Cat c2 = new Cat("王磊");System.out.println(c1.equals(c2)); // false

輸出結果出乎我們的意料,竟然是 false?這是怎麼回事,看了 equals 源碼就知道了,源碼如下:

public boolean equals(Object obj) {    return (this == obj);}

原來 equals 本質上就是 ==。

那問題來了,兩個相同值的 String 對象,為什麼返回的是 true?代碼如下:

String s1 = new String("老王");String s2 = new String("老王");System.out.println(s1.equals(s2)); // true

同樣的,當我們進入 String 的 equals 方法,找到了答案,代碼如下:

原來是 String 重寫了 Object 的 equals 方法,把引用比較改成了值比較。

總結 :== 對於基本類型來說是值比較,對於引用類型來說是比較的是引用;而 equals 默認情況下是引用比較,只是很多類重新了 equals 方法,比如 String、Integer 等把它變成了值比較,所以一般情況下 equals 比較的是值是否相等。

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

執行的結果:

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

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

final 修飾的類叫最終類,該類不能被繼承。

final 修飾的方法不能被重寫。

final 修飾的變量叫常量,常量必須初始化,初始化之後值就不能被修改。

5. java 中的 Math.round(-1.5) 等於多少?

等於 -1,因為在數軸上取值時,中間值(0.5)向右取整,所以正 0.5 是往上取整,負 0.5 是直接捨棄。

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

String 不屬於基礎類型,基礎類型有 8 種:byte、boolean、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 reverseStringBuffer stringBuffer = new StringBuffer();stringBuffer.append("abcdefg");System.out.println(stringBuffer.reverse()); // gfedcba// StringBuilder reverseStringBuilder 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 不能修飾抽象類,如下圖所示,編輯器也會提示錯誤信息:

2020最新Java面試題,常見面試題及答案彙總

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 容器都有哪些?

常用容器的圖錄:

2020最新Java面試題,常見面試題及答案彙總

19. Collection 和 Collections 有什麼區別?

  • java.util.Collection 是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進行基本操作的通用接口方法。Collection接口在Java 類庫中有很多具體的實現。Collection接口的意義是為各種具體的集合提供了最大化的統一操作方式,其直接繼承接口有List與Set。
  • Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜索以及線程安全等各種操作。

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

2020最新Java面試題,常見面試題及答案彙總

21. HashMap 和 Hashtable 有什麼區別?

hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。

hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。

hashMap允許空鍵值,而hashTable不允許。

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

對於在Map中插入、刪除和定位元素這類操作,HashMap是最好的選擇。然而,假如你需要對一個有序的key集合進行遍歷,TreeMap是更好的選擇。基於你的collection的大小,也許向HashMap中添加元素會更快,將map換為TreeMap進行有序key的遍歷。

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

  • HashMap概述: HashMap是基於哈希表的Map接口的非同步實現。此實現提供所有可選的映射操作,並允許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。
  • HashMap的數據結構: 在java編程語言中,最基本的結構就是兩種,一個是數組,另外一個是模擬指針(引用),所有的數據結構都可以用這兩個基本結構來構造的,HashMap也不例外。HashMap實際上是一個“鏈表散列”的數據結構,即數組和鏈表的結合體。

當我們往Hashmap中put元素時,首先根據key的hashcode重新計算hash值,根絕hash值得到這個元素在數組中的位置(下標),如果該數組在該位置上已經存放了其他元素,那麼在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放入鏈尾.如果數組中該位置沒有元素,就直接將該元素放到數組的該位置上。

需要注意Jdk 1.8中對HashMap的實現做了優化,當鏈表中的節點數據超過八個之後,該鏈表會轉為紅黑樹來提高查詢效率,從原來的O(n)到O(logn)

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

  • HashSet底層由HashMap實現
  • HashSet的值存放於HashMap的key上
  • HashMap的value統一為PRESENT

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

最明顯的區別是 ArrrayList底層的數據結構是數組,支持隨機訪問,而 LinkedList 的底層數據結構是雙向循環鏈表,不支持隨機訪問。使用下標訪問一個元素,ArrayList 的時間複雜度是 O(1),而 LinkedList 是 O(n)。

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

  • List轉換成為數組:調用ArrayList的toArray方法。
  • 數組轉換成為List:調用Arrays的asList方法。

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

Vector是同步的,而ArrayList不是。然而,如果你尋求在迭代的時候對列表進行改變,你應該使用CopyOnWriteArrayList。

ArrayList比Vector快,它因為有同步,不會過載。

ArrayList更加通用,因為我們可以使用Collections工具類輕易地獲取同步列表和只讀列表。

28. Array 和 ArrayList 有何區別?

Array可以容納基本類型和對象,而ArrayList只能容納對象。

Array是指定大小的,而ArrayList大小是固定的。

Array沒有提供ArrayList那麼多功能,比如addAll、removeAll和iterator等。

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

poll() 和 remove() 都是從隊列中取出一個元素,但是 poll() 在獲取元素失敗的時候會返回空,但是 remove() 失敗的時候會拋出異常。

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

  • vector:就比arraylist多了個同步化機制(線程安全),因為效率較低,現在已經不太建議使用。在web應用中,特別是前臺頁面,往往效率(頁面響應速度)是優先考慮的。
  • statck:堆棧類,先進後出。
  • hashtable:就比hashmap多了個線程安全。
  • enumeration:枚舉,相當於迭代器。

31. 迭代器 Iterator 是什麼?

迭代器是一種設計模式,它是一個對象,它可以遍歷並選擇序列中的對象,而開發人員不需要了解該序列的底層結構。迭代器通常被稱為“輕量級”對象,因為創建它的代價小。

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

Java中的Iterator功能比較簡單,並且只能單向移動:

(1) 使用方法iterator()要求容器返回一個Iterator。第一次調用Iterator的next()方法時,它返回序列的第一個元素。注意:iterator()方法是java.lang.Iterable接口,被Collection繼承。

(2) 使用next()獲得序列中的下一個元素。

(3) 使用hasNext()檢查序列中是否還有元素。

(4) 使用remove()將迭代器新返回的元素刪除。

Iterator是Java迭代器最簡單的實現,為List設計的ListIterator具有更多的功能,它可以從兩個方向遍歷List,也可以從List中插入和刪除元素。

33. Iterator 和 ListIterator 有什麼區別?

Iterator可用來遍歷Set和List集合,但是ListIterator只能用來遍歷List。

Iterator對集合只能是前向遍歷,ListIterator既可以前向也可以後向。

ListIterator實現了Iterator接口,幷包含其他的功能,比如:增加元素,替換元素,獲取前一個和後一個元素的索引,等等。

三、多線程

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

並行是指兩個或者多個事件在同一時刻發生;而併發是指兩個或多個事件在同一時間間隔發生。

並行是在不同實體上的多個事件,併發是在同一實體上的多個事件。

在一臺處理器上“同時”處理多個任務,在多臺處理器上同時處理多個任務。如hadoop分佈式集群。

所以併發編程的目標是充分的利用處理器的每一個核,以達到最高的處理性能。

36. 線程和進程的區別?

簡而言之,進程是程序運行和資源分配的基本單位,一個程序至少有一個進程,一個進程至少有一個線程。進程在執行過程中擁有獨立的內存單元,而多個線程共享內存資源,減少切換次數,從而效率更高。線程是進程的一個實體,是cpu調度和分派的基本單位,是比程序更小的能獨立運行的基本單位。同一進程中的多個線程之間可以併發執行。

37. 守護線程是什麼?

守護線程(即daemon thread),是個服務線程,準確地來說就是服務其他的線程。

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

①. 繼承Thread類創建線程類

定義Thread類的子類,並重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務。因此把run()方法稱為執行體。

創建Thread子類的實例,即創建了線程對象。

調用線程對象的start()方法來啟動該線程。

②. 通過Runnable接口創建線程類

定義runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。

創建 Runnable實現類的實例,並依此實例作為Thread的target來創建Thread對象,該Thread對象才是真正的線程對象。

調用線程對象的start()方法來啟動該線程。

③. 通過Callable和Future創建線程

創建Callable接口的實現類,並實現call()方法,該call()方法將作為線程執行體,並且有返回值。

創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。

使用FutureTask對象作為Thread對象的target創建並啟動新線程。

調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值。

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

有點深的問題了,也看出一個Java程序員學習知識的廣度。

Runnable接口中的run()方法的返回值是void,它做的事情只是純粹地去執行run()方法中的代碼而已;

Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果。

40. 線程有哪些狀態?

線程通常都有五種狀態,創建、就緒、運行、阻塞和死亡。

  • 創建狀態。在生成線程對象,並沒有調用該對象的start方法,這是線程處於創建狀態。
  • 就緒狀態。當調用了線程對象的start方法之後,該線程就進入了就緒狀態,但是此時線程調度程序還沒有把該線程設置為當前線程,此時處於就緒狀態。在線程運行之後,從等待或者睡眠中回來之後,也會處於就緒狀態。
  • 運行狀態。線程調度程序將處於就緒狀態的線程設置為當前線程,此時線程就進入了運行狀態,開始運行run函數當中的代碼。
  • 阻塞狀態。線程正在運行的時候,被暫停,通常是為了等待某個時間的發生(比如說某項資源就緒)之後再繼續運行。sleep,suspend,wait等方法都可以導致線程阻塞。
  • 死亡狀態。如果一個線程的run方法執行結束或者調用stop方法後,該線程就會死亡。對於已經死亡的線程,無法再使用start方法令其進入就緒   

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

  • sleep():方法是線程類(Thread)的靜態方法,讓調用線程進入睡眠狀態,讓出執行機會給其他線程,等到休眠時間結束後,線程進入就緒狀態和其他線程一起競爭cpu的執行時間。因為sleep() 是static靜態的方法,他不能改變對象的機鎖,當一個synchronized塊中調用了sleep() 方法,線程雖然進入休眠,但是對象的機鎖沒有被釋放,其他線程依然無法訪問這個對象。
  • wait():wait()是Object類的方法,當一個線程執行到wait方法時,它就進入到一個和該對象相關的等待池,同時釋放對象的機鎖,使得其他線程能夠訪問,可以通過notify,notifyAll方法來喚醒等待的線程。

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

如果線程調用了對象的 wait()方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。

當有線程調用了對象的 notifyAll()方法(喚醒所有 wait 線程)或 notify()方法(只隨機喚醒一個 wait 線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。也就是說,調用了notify後只要一個線程會由等待池進入鎖池,而notifyAll會將該對象等待池內的所有線程移動到鎖池中,等待鎖競爭。

優先級高的線程競爭到對象鎖的概率大,假若某線程沒有競爭到該對象鎖,它還會留在鎖池中,唯有線程再次調用 wait()方法,它才會重新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,直到執行完了 synchronized 代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。

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

每個線程都是通過某個特定Thread對象所對應的方法run()來完成其操作的,方法run()稱為線程體。通過調用Thread類的start()方法來啟動一個線程。

  • start()方法來啟動一個線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,可以直接繼續執行下面的代碼; 這時此線程是處於就緒狀態, 並沒有運行。 然後通過此Thread類調用方法run()來完成其運行狀態, 這裡方法run()稱為線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。然後CPU再調度其它線程。
  • run()方法是在本線程裡的,只是線程裡的一個函數,而不是多線程的。 如果直接調用run(),其實就相當於是調用了一個普通函數而已,直接待用run()方法必須等待run()方法執行完畢才能執行下面的代碼,所以執行路徑還是隻有一條,根本就沒有線程的特徵,所以在多線程執行時要使用start()方法而不是run()方法。

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

①. newFixedThreadPool(int nThreads)

創建一個固定長度的線程池,每當提交一個任務就創建一個線程,直到達到線程池的最大數量,這時線程規模將不再變化,當線程發生未預期的錯誤而結束時,線程池會補充一個新的線程。

②. newCachedThreadPool()

創建一個可緩存的線程池,如果線程池的規模超過了處理需求,將自動回收空閒線程,而當需求增加時,則可以自動添加新線程,線程池的規模不存在任何限制。

③. newSingleThreadExecutor()

這是一個單線程的Executor,它創建單個工作線程來執行任務,如果這個線程異常結束,會創建一個新的來替代它;它的特點是能確保依照任務在隊列中的順序來串行執行。

④. newScheduledThreadPool(int corePoolSize)

創建了一個固定長度的線程池,而且以延遲或定時的方式來執行任務,類似於Timer。

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

線程池有5種狀態:Running、ShutDown、Stop、Tidying、Terminated。

線程池各個狀態切換框架圖:

2020最新Java面試題,常見面試題及答案彙總

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

  • 接收的參數不一樣
  • submit有返回值,而execute沒有
  • submit方便Exception處理

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

線程安全在三個方面體現:

  • 原子性:提供互斥訪問,同一時刻只能有一個線程對數據進行操作,(atomic,synchronized);
  • 可見性:一個線程對主內存的修改可以及時地被其他線程看到,(synchronized,volatile);
  • 有序性:一個線程觀察其他線程中的指令執行順序,由於指令重排序,該觀察結果一般雜亂無序,(happens-before原則)。
  • 48. 多線程鎖的升級原理是什麼?

在Java中,鎖共有4種狀態,級別從低到高依次為:無狀態鎖,偏向鎖,輕量級鎖和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。鎖可以升級但不能降級。

鎖升級的圖示過程:


2020最新Java面試題,常見面試題及答案彙總

49. 什麼是死鎖?

死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。是操作系統層面的一個錯誤,是進程死鎖的簡稱,最早在 1965 年由 Dijkstra 在研究銀行家算法時提出的,它是計算機操作系統乃至整個併發程序設計領域最難處理的問題之一。

50. 怎麼防止死鎖?

死鎖的四個必要條件:

  • 互斥條件:進程對所分配到的資源不允許其他進程進行訪問,若其他進程訪問該資源,只能等待,直至佔有該資源的進程使用完成後釋放該資源
  • 請求和保持條件:進程獲得一定的資源之後,又對其他資源發出請求,但是該資源可能被其他進程佔有,此事請求阻塞,但又對自己獲得的資源保持不放
  • 不可剝奪條件:是指進程已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用完後自己釋放
  • 環路等待條件:是指進程發生死鎖後,若干進程之間形成一種頭尾相接的循環等待資源關係

這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之 一不滿足,就不會發生死鎖。

理解了死鎖的原因,尤其是產生死鎖的四個必要條件,就可以最大可能地避免、預防和 解除死鎖。

所以,在系統設計、進程調度等方面注意如何不讓這四個必要條件成立,如何確 定資源的合理分配算法,避免進程永久佔據系統資源。

此外,也要防止進程在處於等待狀態的情況下佔用資源。因此,對資源的分配要給予合理的規劃。

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

線程局部變量是侷限於線程內部的變量,屬於線程自身所有,不在多個線程間共享。Java提供ThreadLocal類來支持線程局部變量,是一種實現線程安全的方式。但是在管理環境下(如 web 服務器)使用線程局部變量的時候要特別小心,在這種情況下,工作線程的生命週期比任何應用變量的生命週期都要長。任何線程局部變量一旦在工作完成後沒有釋放,Java 應用就存在內存洩露的風險。

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

synchronized可以保證方法或者代碼塊在運行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變量的內存可見性。

Java中每一個對象都可以作為鎖,這是synchronized實現同步的基礎:

  • 普通同步方法,鎖是當前實例對象
  • 靜態同步方法,鎖是當前類的class對象
  • 同步方法塊,鎖是括號裡面的對象

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

volatile本質是在告訴jvm當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取; synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。

  • volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的。
  • volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性。
  • volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。
  • volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化。

54. synchronized 和 Lock 有什麼區別?

首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;

synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖;

synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;

用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了;

synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可);

Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。

55. synchronized 和 ReentrantLock 區別是什麼?

synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上:

  • ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖
  • ReentrantLock可以獲取各種鎖的信息
  • ReentrantLock可以靈活地實現多路通知

另外,二者的鎖機制其實也是不一樣的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark word。

56. 說一下 atomic 的原理?

Atomic包中的類基本的特性就是在多線程環境下,當有多個線程同時對單個(包括基本類型及引用類型)變量進行操作時,具有排他性,即當多個線程同時對該變量的值進行更新時,僅有一個線程能成功,而未成功的線程可以向自旋鎖一樣,繼續嘗試,一直等到執行成功。

Atomic系列的類中的核心方法都會調用unsafe類中的幾個本地方法。我們需要先知道一個東西就是Unsafe類,全名為:sun.misc.Unsafe,這個類包含了大量的對C代碼的操作,包括很多直接內存分配以及原子操作的調用,而它之所以標記為非安全的,是告訴你這個裡面大量的方法調用都會存在安全隱患,需要小心使用,否則會導致嚴重的後果,例如在通過unsafe分配內存的時候,如果自己指定某些區域可能會導致一些類似C++一樣的指針越界到其他進程的問題。

四、反射

57. 什麼是反射?

反射主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力

Java反射:

在Java運行時環境中,對於任意一個類,能否知道這個類有哪些屬性和方法?對於任意一個對象,能否調用它的任意一個方法

Java反射機制主要提供了以下功能:

  • 在運行時判斷任意一個對象所屬的類。
  • 在運行時構造任意一個類的對象。
  • 在運行時判斷任意一個類所具有的成員變量和方法。
  • 在運行時調用任意一個對象的方法。

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

簡單說就是為了保存在內存中的各種對象的狀態(也就是實例變量,不是方法),並且可以把保存的對象狀態再讀出來。雖然你可以用你自己的各種各樣的方法來保存object states,但是Java給你提供一種應該比你自己好的保存對象狀態的機制,那就是序列化。

什麼情況下需要序列化:

a)當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;

b)當你想用套接字在網絡上傳送對象的時候;

c)當你想通過RMI傳輸對象的時候;

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

動態代理:

當想要給實現了某個接口的類中的方法,加一些額外的處理。比如說加日誌,加事務等。可以給這個類創建一個代理,故名思議就是創建一個新的類,這個類不僅包含原來類方法的功能,而且還在原來的基礎上添加了額外處理的新類。這個代理類並不是定義好的,是動態生成的。具有解耦意義,靈活,擴展性強。

動態代理的應用:

  • Spring的AOP
  • 加事務
  • 加權限
  • 加日誌

60. 怎麼實現動態代理?

首先必須定義一個接口,還要有一個InvocationHandler(將實現接口的類的對象傳遞給它)處理類。再有一個工具類Proxy(習慣性將其稱為代理類,因為調用他的newInstance()可以產生代理對象,其實他只是一個產生代理對象的工具類)。利用到InvocationHandler,拼接代理類源碼,將其編譯生成代理類的二進制碼,利用加載器加載,並將其實例化產生代理對象,最後返回。

五、對象拷貝

61. 為什麼要使用克隆?

想對一個對象進行處理,又想保留原有的數據進行接下來的操作,就需要克隆了,Java語言中克隆針對的是類的實例。

62. 如何實現對象克隆?

有兩種方式:

1). 實現Cloneable接口並重寫Object類中的clone()方法;

2). 實現Serializable接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深度克隆,代碼如下:

import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable; public class MyUtil {     private MyUtil() {        throw new AssertionError();    }     @SuppressWarnings("unchecked")    public static  T clone(T obj) throws Exception {        ByteArrayOutputStream bout = new ByteArrayOutputStream();        ObjectOutputStream oos = new ObjectOutputStream(bout);        oos.writeObject(obj);         ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());        ObjectInputStream ois = new ObjectInputStream(bin);        return (T) ois.readObject();         // 說明:調用ByteArrayInputStream或ByteArrayOutputStream對象的close方法沒有任何意義        // 這兩個基於內存的流只要垃圾回收器清理對象就能夠釋放資源,這一點不同於對外部資源(如文件流)的釋放    }}

下面是測試代碼:

 
import java.io.Serializable; /** * 人類 * @author nnngu * */class Person implements Serializable { private static final long serialVersionUID = -9102017020286042305L; private String name; // 姓名 private int age; // 年齡 private Car car; // 座駕 public Person(String name, int age, Car car) { this.name = name; this.age = age; this.car = car; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Car getCar() { return car; } public void setCar(Car car) { this.car = car; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", car=" + car + "]"; }}

/** * 小汽車類 * @author nnngu * */class Car implements Serializable {    private static final long serialVersionUID = -5713945027627603702L;     private String brand;       // 品牌    private int maxSpeed;       // 最高時速     public Car(String brand, int maxSpeed) {        this.brand = brand;        this.maxSpeed = maxSpeed;    }     public String getBrand() {        return brand;    }     public void setBrand(String brand) {        this.brand = brand;    }     public int getMaxSpeed() {        return maxSpeed;    }     public void setMaxSpeed(int maxSpeed) {        this.maxSpeed = maxSpeed;    }     @Override    public String toString() {        return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";    }}
class CloneTest {     public static void main(String[] args) {        try {            Person p1 = new Person("郭靖", 33, new Car("Benz", 300));            Person p2 = MyUtil.clone(p1);   // 深度克隆            p2.getCar().setBrand("BYD");            // 修改克隆的Person對象p2關聯的汽車對象的品牌屬性            // 原來的Person對象p1關聯的汽車不會受到任何影響            // 因為在克隆Person對象時其關聯的汽車對象也被克隆了            System.out.println(p1);        } catch (Exception e) {            e.printStackTrace();        }    }}

注意:基於序列化和反序列化實現的克隆不僅僅是深度克隆,更重要的是通過泛型限定,可以檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object類的clone方法克隆對象。讓問題在編譯的時候暴露出來總是好過把問題留到運行時。

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

淺拷貝只是複製了對象的引用地址,兩個對象指向同一個內存地址,所以修改其中任意的值,另一個值都會隨之變化,這就是淺拷貝(例:assign())

深拷貝是將對象及值複製過來,兩個對象修改其中任意的值另一個值不會改變,這就是深拷貝(例:JSON.parse()和JSON.stringify(),但是此方法無法複製函數類型)

六、Java Web

64. jsp 和 servlet 有什麼區別?

  1. jsp經編譯後就變成了Servlet.(JSP的本質就是Servlet,JVM只能識別java的類,不能識別JSP的代碼,Web容器將JSP的代碼編譯成JVM能夠識別的java類)
  2. jsp更擅長表現於頁面顯示,servlet更擅長於邏輯控制。
  3. Servlet中沒有內置對象,Jsp中的內置對象都是必須通過HttpServletRequest對象,HttpServletResponse對象以及HttpServlet對象得到。
  4. Jsp是Servlet的一種簡化,使用Jsp只需要完成程序員需要輸出到客戶端的內容,Jsp中的Java腳本如何鑲嵌到一個類中,由Jsp容器完成。而Servlet則是個完整的Java類,這個類的Service方法用於生成對客戶端的響應。

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

JSP有9個內置對象:

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

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

JSP中的四種作用域包括page、request、session和application,具體來說:

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

67. session 和 cookie 有什麼區別?

  • 由於HTTP協議是無狀態的協議,所以服務端需要記錄用戶的狀態時,就需要用某種機制來識具體的用戶,這個機制就是Session.典型的場景比如購物車,當你點擊下單按鈕時,由於HTTP協議無狀態,所以並不知道是哪個用戶操作的,所以服務端要為特定的用戶創建了特定的Session,用用於標識這個用戶,並且跟蹤用戶,這樣才知道購物車裡面有幾本書。這個Session是保存在服務端的,有一個唯一標識。在服務端保存Session的方法很多,內存、數據庫、文件都有。集群的時候也要考慮Session的轉移,在大型的網站,一般會有專門的Session服務器集群,用來保存用戶會話,這個時候 Session 信息都是放在內存的,使用一些緩存服務比如Memcached之類的來放 Session。
  • 思考一下服務端如何識別特定的客戶?這個時候Cookie就登場了。每次HTTP請求的時候,客戶端都會發送相應的Cookie信息到服務端。實際上大多數的應用都是用 Cookie 來實現Session跟蹤的,第一次創建Session的時候,服務端會在HTTP協議中告訴客戶端,需要在 Cookie 裡面記錄一個Session ID,以後每次請求把這個會話ID發送到服務器,我就知道你是誰了。有人問,如果客戶端的瀏覽器禁用了 Cookie 怎麼辦?一般這種情況下,會使用一種叫做URL重寫的技術來進行會話跟蹤,即每次HTTP交互,URL後面都會被附加上一個諸如 sid=xxxxx 這樣的參數,服務端據此來識別用戶。
  • Cookie其實還可以用在一些方便用戶的場景下,設想你某次登陸過一個網站,下次登錄的時候不想再次輸入賬號了,怎麼辦?這個信息可以寫到Cookie裡面,訪問網站的時候,網站頁面的腳本可以讀取這個信息,就自動幫你把用戶名給填了,能夠方便一下用戶。這也是Cookie名稱的由來,給用戶的一點甜頭。所以,總結一下:Session是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據可以保存在集群、數據庫、文件中;Cookie是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現Session的一種方式。

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

其實session是一個存在服務器上的類似於一個散列表格的文件。裡面存有我們需要的信息,在我們需要用的時候可以從裡面取出來。類似於一個大號的map吧,裡面的鍵存儲的是用戶的sessionid,用戶向服務器發送請求的時候會帶上這個sessionid。這時就可以從中取出對應的值了。

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

Cookie與 Session,一般認為是兩個獨立的東西,Session採用的是在服務器端保持狀態的方案,而Cookie採用的是在客戶端保持狀態的方案。但為什麼禁用Cookie就不能得到Session呢?因為Session是用Session ID來確定當前對話所對應的服務器Session,而Session ID是通過Cookie來傳遞的,禁用Cookie相當於失去了Session ID,也就得不到Session了。

假定用戶關閉Cookie的情況下使用Session,其實現途徑有以下幾種:

  1. 設置php.ini配置文件中的“session.use_trans_sid = 1”,或者編譯時打開打開了“--enable-trans-sid”選項,讓PHP自動跨頁傳遞Session ID。
  2. 手動通過URL傳值、隱藏表單傳遞Session ID。
  3. 用文件、數據庫等形式保存Session ID,在跨頁過程中手動調用。

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

  • 攔截機制的不同

Struts2是類級別的攔截,每次請求就會創建一個Action,和Spring整合時Struts2的ActionBean注入作用域是原型模式prototype,然後通過setter,getter吧request數據注入到屬性。Struts2中,一個Action對應一個request,response上下文,在接收參數時,可以通過屬性接收,這說明屬性參數是讓多個方法共享的。Struts2中Action的一個方法可以對應一個url,而其類屬性卻被所有方法共享,這也就無法用註解或其他方式標識其所屬方法了,只能設計為多例。

SpringMVC是方法級別的攔截,一個方法對應一個Request上下文,所以方法直接基本上是獨立的,獨享request,response數據。而每個方法同時又何一個url對應,參數的傳遞是直接注入到方法中的,是方法所獨有的。處理結果通過ModeMap返回給框架。在Spring整合時,SpringMVC的Controller Bean默認單例模式Singleton,所以默認對所有的請求,只會創建一個Controller,有應為沒有共享的屬性,所以是線程安全的,如果要改變默認的作用域,需要添加@Scope註解修改。

Struts2有自己的攔截Interceptor機制,SpringMVC這是用的是獨立的Aop方式,這樣導致Struts2的配置文件量還是比SpringMVC大。

  • 底層框架的不同

Struts2採用Filter(StrutsPrepareAndExecuteFilter)實現,SpringMVC(DispatcherServlet)則採用Servlet實現。Filter在容器啟動之後即初始化;服務停止以後墜毀,晚於Servlet。Servlet在是在調用時初始化,先於Filter調用,服務停止後銷燬。

  • 性能方面

Struts2是類級別的攔截,每次請求對應實例一個新的Action,需要加載所有的屬性值注入,SpringMVC實現了零配置,由於SpringMVC基於方法的攔截,有加載一次單例模式bean注入。所以,SpringMVC開發效率和性能高於Struts2。

  • 配置方面

spring MVC和Spring是無縫的。從這個項目的管理和安全上也比Struts2高。

71. 如何避免 sql 注入?

  1. PreparedStatement(簡單又有效的方法)
  2. 使用正則表達式過濾傳入的參數
  3. 字符串過濾
  4. JSP中調用該函數檢查是否包函非法字符
  5. JSP頁面判斷代碼

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

XSS攻擊又稱CSS,全稱Cross Site Script (跨站腳本攻擊),其原理是攻擊者向有XSS漏洞的網站中輸入惡意的 HTML 代碼,當用戶瀏覽該網站時,這段 HTML 代碼會自動執行,從而達到攻擊的目的。XSS 攻擊類似於 SQL 注入攻擊,SQL注入攻擊中以SQL語句作為用戶輸入,從而達到查詢/修改/刪除數據的目的,而在xss攻擊中,通過插入惡意腳本,實現對用戶遊覽器的控制,獲取用戶的一些信息。 XSS是 Web 程序中常見的漏洞,XSS 屬於被動式且用於客戶端的攻擊方式。

XSS防範的總體思路是:對輸入(和URL參數)進行過濾,對輸出進行編碼。

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

CSRF(Cross-site request forgery)也被稱為 one-click attack或者 session riding,中文全稱是叫跨站請求偽造。一般來說,攻擊者通過偽造用戶的瀏覽器的請求,向訪問一個用戶自己曾經認證訪問過的網站發送出去,使目標網站接收並誤以為是用戶的真實操作而去執行命令。常用於盜取賬號、轉賬、發送虛假消息等。攻擊者利用網站對請求的驗證漏洞而實現這樣的攻擊行為,網站能夠確認請求來源於用戶的瀏覽器,卻不能驗證請求是否源於用戶的真實意願下的操作行為。

如何避免:

1. 驗證 HTTP Referer 字段

HTTP頭中的Referer字段記錄了該 HTTP 請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求來自於同一個網站,而如果黑客要對其實施 CSRF
攻擊,他一般只能在他自己的網站構造請求。因此,可以通過驗證Referer值來防禦CSRF 攻擊。

2. 使用驗證碼

關鍵操作頁面加上驗證碼,後臺收到請求後通過判斷驗證碼可以防禦CSRF。但這種方法對用戶不太友好。

3. 在請求地址中添加token並驗證

CSRF 攻擊之所以能夠成功,是因為黑客可以完全偽造用戶的請求,該請求中所有的用戶驗證信息都是存在於cookie中,因此黑客可以在不知道這些驗證信息的情況下直接利用用戶自己的cookie 來通過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能偽造的信息,並且該信息不存在於 cookie 之中。可以在 HTTP 請求中以參數的形式加入一個隨機產生的 token,並在服務器端建立一個攔截器來驗證這個 token,如果請求中沒有token或者 token 內容不正確,則認為可能是 CSRF 攻擊而拒絕該請求。這種方法要比檢查 Referer 要安全一些,token 可以在用戶登陸後產生並放於session之中,然後在每次請求時把token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在於如何把 token 以參數的形式加入請求。

對於 GET 請求,token 將附在請求地址之後,這樣 URL 就變成 http://url?csrftoken=tokenvalue。

而對於 POST 請求來說,要在 form 的最後加上 ,這樣就把token以參數的形式加入請求了。

4. 在HTTP 頭中自定義屬性並驗證

這種方法也是使用 token 並進行驗證,和上一種方法不同的是,這裡並不是把 token 以參數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性裡。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔心 token 會透過 Referer 洩露到其他網站中去。

七、異常

74. throw 和 throws 的區別?

throws是用來聲明一個方法可能拋出的所有異常信息,throws是將異常聲明但是不處理,而是將異常往上傳,誰調用我就交給誰處理。而throw則是指拋出的一個具體的異常類型。

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

  • final可以修飾類、變量、方法,修飾類表示該類不能被繼承、修飾方法表示該方法不能被重寫、修飾變量表示該變量是一個常量不能被重新賦值。
  • finally一般作用在try-catch代碼塊中,在處理異常的時候,通常我們將一定要執行的代碼方法finally代碼塊中,表示不管是否出現異常,該代碼塊都會執行,一般用來存放一些關閉資源的代碼。
  • finalize是一個方法,屬於Object類的一個方法,而Object類是所有類的父類,該方法一般由垃圾回收器來調用,當我們調用System的gc()方法的時候,由垃圾回收器調用finalize(),回收垃圾。

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

答:catch 可以省略

原因:

更為嚴格的說法其實是:try只適合處理運行時異常,try+catch適合處理運行時異常+普通異常。也就是說,如果你只用try去處理普通異常卻不加以catch處理,編譯是通不過的,因為編譯器硬性規定,普通異常如果選擇捕獲,則必須用catch顯示聲明以便進一步處理。而運行時異常在編譯時沒有如此規定,所以catch可以省略,你加上catch編譯器也覺得無可厚非。

理論上,編譯器看任何代碼都不順眼,都覺得可能有潛在的問題,所以你即使對所有代碼加上try,代碼在運行期時也只不過是在正常運行的基礎上加一層皮。但是你一旦對一段代碼加上try,就等於顯示地承諾編譯器,對這段代碼可能拋出的異常進行捕獲而非向上拋出處理。如果是普通異常,編譯器要求必須用catch捕獲以便進一步處理;如果運行時異常,捕獲然後丟棄並且+finally掃尾處理,或者加上catch捕獲以便進一步處理。

至於加上finally,則是在不管有沒捕獲異常,都要進行的“掃尾”處理。

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

答:會執行,在 return 前執行。

執行結果:30

代碼示例2:

執行結果:40

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

  • NullPointerException:當應用程序試圖訪問空對象時,則拋出該異常。
  • SQLException:提供關於數據庫訪問錯誤或其他錯誤信息的異常。
  • IndexOutOfBoundsException:指示某排序索引(例如對數組、字符串或向量的排序)超出範圍時拋出。
  • NumberFormatException:當應用程序試圖將字符串轉換成一種數值類型,但該字符串不能轉換為適當格式時,拋出該異常。
  • FileNotFoundException:當試圖打開指定路徑名錶示的文件失敗時,拋出此異常。
  • IOException:當發生某種I/O異常時,拋出此異常。此類是失敗或中斷的I/O操作生成的異常的通用類。
  • ClassCastException:當試圖將對象強制轉換為不是實例的子類時,拋出該異常。
  • ArrayStoreException:試圖將錯誤類型的對象存儲到一個對象數組時拋出的異常。
  • IllegalArgumentException:拋出的異常表明向方法傳遞了一個不合法或不正確的參數。
  • ArithmeticException:當出現異常的運算條件時,拋出此異常。例如,一個整數“除以零”時,拋出此類的一個實例。
  • NegativeArraySizeException:如果應用程序試圖創建大小為負的數組,則拋出該異常。
  • NoSuchMethodException:無法找到某一特定方法時,拋出該異常。
  • SecurityException:由安全管理器拋出的異常,指示存在安全侵犯。
  • UnsupportedOperationException:當不支持請求的操作時,拋出該異常。
  • RuntimeExceptionRuntimeException:是那些可能在Java虛擬機正常運行期間拋出的異常的超類。

八、網絡

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

答:301,302 都是HTTP狀態的編碼,都代表著某個URL發生了轉移。

區別:

  1. 301 redirect: 301 代表永久性轉移(Permanently Moved)。
  2. 302 redirect: 302 代表暫時性轉移(Temporarily Moved )。

80. forward 和 redirect 的區別?

Forward和Redirect代表了兩種請求轉發方式:直接轉發和間接轉發。

直接轉發方式(Forward),客戶端和瀏覽器只發出一次請求,Servlet、HTML、JSP或其它信息資源,由第二個信息資源響應該請求,在請求對象request中,保存的對象對於每個信息資源是共享的。

間接轉發方式(Redirect)實際是兩次HTTP請求,服務器端在響應第一次請求的時候,讓瀏覽器再向另外一個URL發出請求,從而達到轉發的目的。

舉個通俗的例子:

直接轉發就相當於:“A找B借錢,B說沒有,B去找C借,借到借不到都會把消息傳遞給A”;

間接轉發就相當於:"A找B借錢,B說沒有,讓A去找C借"。

81. 簡述 tcp 和 udp的區別?

  • TCP面向連接(如打電話要先撥號建立連接);UDP是無連接的,即發送數據之前不需要建立連接。
  • TCP提供可靠的服務。也就是說,通過TCP連接傳送的數據,無差錯,不丟失,不重複,且按序到達;UDP盡最大努力交付,即不保證可靠交付。
  • Tcp通過校驗和,重傳控制,序號標識,滑動窗口、確認應答實現可靠傳輸。如丟包時的重發控制,還可以對次序亂掉的分包進行順序控制。
  • UDP具有較好的實時性,工作效率比TCP高,適用於對高速傳輸和實時性有較高的通信或廣播通信。
  • 每一條TCP連接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互通信。
  • TCP對系統資源要求較多,UDP對系統資源要求較少。

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

為了實現可靠數據傳輸, TCP 協議的通信雙方, 都必須維護一個序列號, 以標識發送出去的數據包中, 哪些是已經被對方收到的。 三次握手的過程即是通信雙方相互告知序列號起始值, 並確認對方已經收到了序列號起始值的必經步驟。

如果只是兩次握手, 至多隻有連接發起方的起始序列號能被確認, 另一方選擇的序列號則得不到確認。

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

①. 發送方產生粘包

採用TCP協議傳輸數據的客戶端與服務器經常是保持一個長連接的狀態(一次連接發一次數據不存在粘包),雙方在連接不斷開的情況下,可以一直傳輸數據;但當發送的數據包過於的小時,那麼TCP協議默認的會啟用Nagle算法,將這些較小的數據包進行合併發送(緩衝區數據發送是一個堆壓的過程);這個合併過程就是在發送緩衝區中進行的,也就是說數據發送出來它已經是粘包的狀態了。

2020最新Java面試題,常見面試題及答案彙總

②. 接收方產生粘包

接收方採用TCP協議接收數據時的過程是這樣的:數據到底接收方,從網絡模型的下方傳遞至傳輸層,傳輸層的TCP協議處理是將其放置接收緩衝區,然後由應用層來主動獲取(C語言用recv、read等函數);這時會出現一個問題,就是我們在程序中調用的讀取數據函數不能及時的把緩衝區中的數據拿出來,而下一個數據又到來並有一部分放入的緩衝區末尾,等我們讀取數據時就是一個粘包。(放數據的速度 > 應用層拿數據速度)

2020最新Java面試題,常見面試題及答案彙總

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

  1. 應用層:網絡服務與最終用戶的一個接口。
  2. 表示層:數據的表示、安全、壓縮。
  3. 會話層:建立、管理、終止會話。
  4. 傳輸層:定義傳輸數據的協議端口號,以及流控和差錯校驗。
  5. 網絡層:進行邏輯地址尋址,實現不同網絡之間的路徑選擇。
  6. 數據鏈路層:建立邏輯連接、進行硬件地址尋址、差錯校驗等功能。
  7. 物理層:建立、維護、斷開物理連接。

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

  • GET在瀏覽器回退時是無害的,而POST會再次提交請求。
  • GET產生的URL地址可以被Bookmark,而POST不可以。
  • GET請求會被瀏覽器主動cache,而POST不會,除非手動設置。
  • GET請求只能進行url編碼,而POST支持多種編碼方式。
  • GET請求參數會被完整保留在瀏覽器歷史記錄裡,而POST中的參數不會被保留。
  • GET請求在URL中傳送的參數是有長度限制的,而POST麼有。
  • 對參數的數據類型,GET只接受ASCII字符,而POST沒有限制。
  • GET比POST更不安全,因為參數直接暴露在URL上,所以不能用來傳遞敏感信息。
  • GET參數通過URL傳遞,POST放在Request body中。

86. 如何實現跨域?

  • 方式一:圖片ping或script標籤跨域

圖片ping常用於跟蹤用戶點擊頁面或動態廣告曝光次數。

script標籤可以得到從其他來源數據,這也是JSONP依賴的根據。

  • 方式二:JSONP跨域

JSONP(JSON with Padding)是數據格式JSON的一種“使用模式”,可以讓網頁從別的網域要數據。根據 XmlHttpRequest 對象受到同源策略的影響,而利用 ...


分享到:


相關文章: