發現網上很多Java面試題都沒有答案,所以花了很長時間搜集整理出來了這套Java面試題大全,希望對大家有幫助哈~
博主已將以下這些面試題整理成了一個Java面試手冊,是PDF版的,
關注博主的微信公眾號: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); // 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,
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 方法,找到了答案,代碼如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
原來是 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));
執行的結果:
str1:1179395 | str2:1179395
false
代碼解讀:很顯然“通話”和“重地”的 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 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 不能修飾抽象類,如下圖所示,編輯器也會提示錯誤資訊:
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 容器都有哪些?
常用容器的圖錄:
19. Collection 和 Collections 有什么區別?
- java.util.Collection 是一個集合介面(集合類的一個頂級介面),它提供了對集合物件進行基本操作的通用介面方法,Collection介面在Java 類別庫中有很多具體的實作,Collection介面的意義是為各種具體的集合提供了最大化的統一操作方式,其直接繼承介面有List與Set,
- Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態方法,用于對集合中元素進行排序、搜索以及執行緒安全等各種操作,
20. List、Set、Map 之間的區別是什么?
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,
執行緒池各個狀態切換框架圖:
46. 執行緒池中 submit()和 execute()方法有什么區別?
- 接收的引數不一樣
- submit有回傳值,而execute沒有
- submit方便Exception處理
47. 在 java 程式中怎么保證多執行緒的運行安全?
執行緒安全在三個方面體現:
- 原子性:提供互斥訪問,同一時刻只能有一個執行緒對資料進行操作,(atomic,synchronized);
- 可見性:一個執行緒對主記憶體的修改可以及時地被其他執行緒看到,(synchronized,volatile);
- 有序性:一個執行緒觀察其他執行緒中的指令執行順序,由于指令重排序,該觀察結果一般雜亂無序,(happens-before原則),
48. 多執行緒鎖的升級原理是什么?
在Java中,鎖共有4種狀態,級別從低到高依次為:無狀態鎖,偏向鎖,輕量級鎖和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級,鎖可以升級但不能降級,
鎖升級的圖示程序:
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 extends Serializable> 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 有什么區別?
- jsp經編譯后就變成了Servlet.(JSP的本質就是Servlet,JVM只能識別java的類,不能識別JSP的代碼,Web容器將JSP的代碼編譯成JVM能夠識別的java類)
- jsp更擅長表現于頁面顯示,servlet更擅長于邏輯控制,
- Servlet中沒有內置物件,Jsp中的內置物件都是必須通過HttpServletRequest物件,HttpServletResponse物件以及HttpServlet物件得到,
- Jsp是Servlet的一種簡化,使用Jsp只需要完成程式員需要輸出到客戶端的內容,Jsp中的Java腳本如何鑲嵌到一個類中,由Jsp容器完成,而Servlet則是個完整的Java類,這個類的Service方法用于生成對客戶端的回應,
65. jsp 有哪些內置物件?作用分別是什么?
JSP有9個內置物件:
- request:封裝客戶端的請求,其中包含來自GET或POST請求的引數;
- response:封裝服務器對客戶端的回應;
- pageContext:通過該物件可以獲取其他物件;
- session:封裝用戶會話的物件;
- application:封裝服務器運行環境的物件;
- out:輸出服務器回應的輸出流物件;
- config:Web應用的配置物件;
- page:JSP頁面本身(相當于Java程式中的this);
- 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,其實作途徑有以下幾種:
- 設定php.ini組態檔中的“session.use_trans_sid = 1”,或者編譯時打開打開了“--enable-trans-sid”選項,讓PHP自動跨頁傳遞Session ID,
- 手動通過URL傳值、隱藏表單傳遞Session ID,
- 用檔案、資料庫等形式保存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 注入?
- PreparedStatement(簡單又有效的方法)
- 使用正則運算式過濾傳入的引數
- 字串過濾
- JSP中呼叫該函式檢查是否包函非法字符
- 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 的最后加上 <input type="hidden" name="csrftoken" value="tokenvalue"/>,這樣就把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 前執行,
代碼示例1:
/*
* java面試題--如果catch里面有return陳述句,finally里面的代碼還會執行嗎?
*/
public class FinallyDemo2 {
public static void main(String[] args) {
System.out.println(getInt());
}
public static int getInt() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
/*
* return a 在程式執行到這一步的時候,這里不是return a 而是 return 30;這個回傳路徑就形成了
* 但是呢,它發現后面還有finally,所以繼續執行finally的內容,a=40
* 再次回到以前的路徑,繼續走return 30,形成回傳路徑之后,這里的a就不是a變數了,而是常量30
*/
} finally {
a = 40;
}
// return a;
}
}
執行結果:30
代碼示例2:
package com.java_02;
/*
* java面試題--如果catch里面有return陳述句,finally里面的代碼還會執行嗎?
*/
public class FinallyDemo2 {
public static void main(String[] args) {
System.out.println(getInt());
}
public static int getInt() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
/*
* return a 在程式執行到這一步的時候,這里不是return a 而是 return 30;這個回傳路徑就形成了
* 但是呢,它發現后面還有finally,所以繼續執行finally的內容,a=40
* 再次回到以前的路徑,繼續走return 30,形成回傳路徑之后,這里的a就不是a變數了,而是常量30
*/
} finally {
a = 40;
return a; //如果這樣,就又重新形成了一潭訓傳路徑,由于只能通過1個return回傳,所以這里直接回傳40
}
// return a;
}
}
執行結果: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發生了轉移,
區別:
- 301 redirect: 301 代表永久性轉移(Permanently Moved),
- 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演算法,將這些較小的資料包進行合并發送(緩沖區資料發送是一個堆壓的程序);這個合并程序就是在發送緩沖區中進行的,也就是說資料發送出來它已經是粘包的狀態了,
②. 接收方產生粘包
接收方采用TCP協議接收資料時的程序是這樣的:資料到底接收方,從網路模型的下方傳遞至傳輸層,傳輸層的TCP協議處理是將其放置接識訓沖區,然后由應用層來主動獲取(C語言用recv、read等函式);這時會出現一個問題,就是我們在程式中呼叫的讀取資料函式不能及時的把緩沖區中的資料拿出來,而下一個資料又到來并有一部分放入的緩沖區末尾,等我們讀取資料時就是一個粘包,(放資料的速度 > 應用層拿資料速度)
84. OSI 的七層模型都有哪些?
- 應用層:網路服務與最終用戶的一個介面,
- 表示層:資料的表示、安全、壓縮,
- 會話層:建立、管理、終止會話,
- 傳輸層:定義傳輸資料的協議埠號,以及流控和差錯校驗,
- 網路層:進行邏輯地址尋址,實作不同網路之間的路徑選擇,
- 資料鏈路層:建立邏輯連接、進行硬體地址尋址、差錯校驗等功能,
- 物理層:建立、維護、斷開物理連接,
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 物件受到同源策略的影響,而利用 <script>元素的這個開放策略,網頁可以得到從其他來源動態產生的JSON資料,而這種使用模式就是所謂的 JSONP,用JSONP抓到的資料并不是JSON,而是任意的JavaScript,用 JavaScript解釋器運行而不是用JSON決議器決議,所有,通過Chrome查看所有JSONP發送的Get請求都是js型別,而非XHR,
缺點:
- 只能使用Get請求
- 不能注冊success、error等事件監聽函式,不能很容易的確定JSONP請求是否失敗
- JSONP是從其他域中加載代碼執行,容易受到跨站請求偽造的攻擊,其安全性無法確保
方式三:CORS
Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術的規范,提供了 Web 服務從不同域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,確保安全的跨域資料傳輸,現代瀏覽器使用CORS在API容器如XMLHttpRequest來減少HTTP請求的風險來源,與 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求,服務器一般需要增加如下回應頭的一種或幾種:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
跨域請求默認不會攜帶Cookie資訊,如果需要攜帶,請配置下述引數:
"Access-Control-Allow-Credentials": true
// Ajax設定
"withCredentials": true
方式四:window.name+iframe
window.name通過在iframe(一般動態創建i)中加載跨域HTML檔案來起作用,然后,HTML檔案將傳遞給請求者的字串內容賦值給window.name,然后,請求者可以檢索window.name值作為回應,
- iframe標簽的跨域能力;
- window.name屬性值在檔案重繪后依舊存在的能力(且最大允許2M左右),
每個iframe都有包裹它的window,而這個window是top window的子視窗,contentWindow屬性回傳<iframe>元素的Window物件,你可以使用這個Window物件來訪問iframe的檔案及其內部DOM,
<!--
下述用埠
10000表示:domainA
10001表示:domainB
-->
<!-- localhost:10000 -->
<script>
var iframe = document.createElement('iframe');
iframe.style.display = 'none'; // 隱藏
var state = 0; // 防止頁面無限重繪
iframe.onload = function() {
if(state === 1) {
console.log(JSON.parse(iframe.contentWindow.name));
// 清除創建的iframe
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
} else if(state === 0) {
state = 1;
// 加載完成,指向當前域,防止錯誤(proxy.html為空白頁面)
// Blocked a frame with origin "http://localhost:10000" from accessing a cross-origin frame.
iframe.contentWindow.location = 'http://localhost:10000/proxy.html';
}
};
iframe.src = 'http://localhost:10001';
document.body.appendChild(iframe);
</script>
<!-- localhost:10001 -->
<!DOCTYPE html>
...
<script>
window.name = JSON.stringify({a: 1, b: 2});
</script>
</html>
方式五:window.postMessage()
HTML5新特性,可以用來向其他所有的 window 物件發送訊息,需要注意的是我們必須要保證所有的腳本執行完才發送 MessageEvent,如果在函式執行的程序中呼叫了它,就會讓后面的函式超時無法執行,
下述代碼實作了跨域存盤localStorage
<!--
下述用埠
10000表示:domainA
10001表示:domainB
-->
<!-- localhost:10000 -->
<iframe src="http://localhost:10001/msg.html" name="myPostMessage" style="display:none;">
</iframe>
<script>
function main() {
LSsetItem('test', 'Test: ' + new Date());
LSgetItem('test', function(value) {
console.log('value: ' + value);
});
LSremoveItem('test');
}
var callbacks = {};
window.addEventListener('message', function(event) {
if (event.source === frames['myPostMessage']) {
console.log(event)
var data = /^#localStorage#(\d+)(null)?#([\S\s]*)/.exec(event.data);
if (data) {
if (callbacks[data[1]]) {
callbacks[data[1]](data[2] === 'null' ? null : data[3]);
}
delete callbacks[data[1]];
}
}
}, false);
var domain = '*';
// 增加
function LSsetItem(key, value) {
var obj = {
setItem: key,
value: value
};
frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
}
// 獲取
function LSgetItem(key, callback) {
var identifier = new Date().getTime();
var obj = {
identifier: identifier,
getItem: key
};
callbacks[identifier] = callback;
frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
}
// 洗掉
function LSremoveItem(key) {
var obj = {
removeItem: key
};
frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
}
</script>
<!-- localhost:10001 -->
<script>
window.addEventListener('message', function(event) {
console.log('Receiver debugging', event);
if (event.origin == 'http://localhost:10000') {
var data = JSON.parse(event.data);
if ('setItem' in data) {
localStorage.setItem(data.setItem, data.value);
} else if ('getItem' in data) {
var gotItem = localStorage.getItem(data.getItem);
event.source.postMessage(
'#localStorage#' + data.identifier +
(gotItem === null ? 'null#' : '#' + gotItem),
event.origin
);
} else if ('removeItem' in data) {
localStorage.removeItem(data.removeItem);
}
}
}, false);
</script>
注意Safari一下,會報錯:
Blocked a frame with origin “http://localhost:10001” from accessing a frame with origin “http://localhost:10000“. Protocols, domains, and ports must match.
避免該錯誤,可以在Safari瀏覽器中勾選開發選單==>停用跨域限制,或者只能使用服務器端轉存的方式實作,因為Safari瀏覽器默認只支持CORS跨域請求,
方式六:修改document.domain跨子域
前提條件:這兩個域名必須屬于同一個基礎域名!而且所用的協議,埠都要一致,否則無法利用document.domain進行跨域,所以只能跨子域
在根域范圍內,允許把domain屬性的值設定為它的上一級域,例如,在”aaa.xxx.com”域內,可以把domain設定為 “xxx.com” 但不能設定為 “xxx.org” 或者”com”,
現在存在兩個域名aaa.xxx.com和bbb.xxx.com,在aaa下嵌入bbb的頁面,由于其document.name不一致,無法在aaa下操作bbb的js,可以在aaa和bbb下通過js將document.name = 'xxx.com';設定一致,來達到互相訪問的作用,
方式七:WebSocket
WebSocket protocol 是HTML5一種新的協議,它實作了瀏覽器與服務器全雙工通信,同時允許跨域通訊,是server push技術的一種很棒的實作,相關文章,請查看:WebSocket、WebSocket-SockJS
需要注意:WebSocket物件不支持DOM 2級事件偵聽器,必須使用DOM 0級語法分別定義各個事件,
方式八:代理
同源策略是針對瀏覽器端進行的限制,可以通過服務器端來解決該問題
DomainA客戶端(瀏覽器) ==> DomainA服務器 ==> DomainB服務器 ==> DomainA客戶端(瀏覽器)
來源:blog.csdn.net/ligang2585116/article/details/73072868
87.說一下 JSONP 實作原理?
jsonp 即 json+padding,動態創建script標簽,利用script標簽的src屬性可以獲取任何域下的js腳本,通過這個特性(也可以說漏洞),服務器端不在返貨json格式,而是回傳一段呼叫某個函式的js代碼,在src中進行了呼叫,這樣實作了跨域,
九、設計模式
88. 說一下你熟悉的設計模式?
參考:常用的設計模式匯總,超詳細!
89. 簡單工廠和抽象工廠有什么區別?
簡單工廠模式:
這個模式本身很簡單而且使用在業務較簡單的情況下,一般用于小專案或者具體產品很少擴展的情況(這樣工廠類才不用經常更改),
它由三種角色組成:
- 工廠類角色:這是本模式的核心,含有一定的商業邏輯和判斷邏輯,根據邏輯不同,產生具體的工廠產品,如例子中的Driver類,
- 抽象產品角色:它一般是具體產品繼承的父類或者實作的介面,由介面或者抽象類來實作,如例中的Car介面,
- 具體產品角色:工廠類所創建的物件就是此角色的實體,在java中由一個具體類實作,如例子中的Benz、Bmw類,
來用類圖來清晰的表示下的它們之間的關系:
抽象工廠模式:
先來認識下什么是產品族: 位于不同產品等級結構中,功能相關聯的產品組成的家族,
圖中的BmwCar和BenzCar就是兩個產品樹(產品層次結構);而如圖所示的BenzSportsCar和BmwSportsCar就是一個產品族,他們都可以放到跑車家族中,因此功能有所關聯,同理BmwBussinessCar和BenzBusinessCar也是一個產品族,
可以這么說,它和工廠方法模式的區別就在于需要創建物件的復雜程度上,而且抽象工廠模式是三個里面最為抽象、最具一般性的,抽象工廠模式的用意為:給客戶端提供一個介面,可以創建多個產品族中的產品物件,
而且使用抽象工廠模式還要滿足一下條件:
- 系統中有多個產品族,而系統一次只可能消費其中一族產品
- 同屬于同一個產品族的產品以其使用,
來看看抽象工廠模式的各個角色(和工廠方法的如出一轍):
- 抽象工廠角色: 這是工廠方法模式的核心,它與應用程式無關,是具體工廠角色必須實作的介面或者必須繼承的父類,在java中它由抽象類或者介面來實作,
- 具體工廠角色:它含有和具體業務邏輯有關的代碼,由應用程式呼叫以創建對應的具體產品的物件,在java中它由具體的類來實作,
- 抽象產品角色:它是具體產品繼承的父類或者是實作的介面,在java中一般有抽象類或者介面來實作,
- 具體產品角色:具體工廠角色所創建的物件就是此角色的實體,在java中由具體的類來實作,
十、Spring / Spring MVC
90. 為什么要使用 spring?
1.簡介
- 目的:解決企業應用開發的復雜性
- 功能:使用基本的JavaBean代替EJB,并提供了更多的企業應用功能
- 范圍:任何Java應用
簡單來說,Spring是一個輕量級的控制反轉(IoC)和面向切面(AOP)的容器框架,
2.輕量
從大小與開銷兩方面而言Spring都是輕量的,完整的Spring框架可以在一個大小只有1MB多的JAR檔案里發布,并且Spring所需的處理開銷也是微不足道的,此外,Spring是非侵入式的:典型地,Spring應用中的物件不依賴于Spring的特定類,
3.控制反轉
Spring通過一種稱作控制反轉(IoC)的技術促進了松耦合,當應用了IoC,一個物件依賴的其它物件會通過被動的方式傳遞進來,而不是這個物件自己創建或者查找依賴物件,你可以認為IoC與JNDI相反——不是物件從容器中查找依賴,而是容器在物件初始化時不等物件請求就主動將依賴傳遞給它,
4.面向切面
Spring提供了面向切面編程的豐富支持,允許通過分離應用的業務邏輯與系統級服務(例如審計(auditing)和事務(transaction)管理)進行內聚性的開發,應用物件只實作它們應該做的——完成業務邏輯——僅此而已,它們并不負責(甚至是意識)其它的系統級關注點,例如日志或事務支持,
5.容器
Spring包含并管理應用物件的配置和生命周期,在這個意義上它是一種容器,你可以配置你的每個bean如何被創建——基于一個可配置原型(prototype),你的bean可以創建一個單獨的實體或者每次需要時都生成一個新的實體——以及它們是如何相互關聯的,然而,Spring不應該被混同于傳統的重量級的EJB容器,它們經常是龐大與笨重的,難以使用,
6.框架
Spring可以將簡單的組件配置、組合成為復雜的應用,在Spring中,應用物件被宣告式地組合,典型地是在一個XML檔案里,Spring也提供了很多基礎功能(事務管理、持久化框架集成等等),將應用邏輯的開發留給了你,
所有Spring的這些特征使你能夠撰寫更干凈、更可管理、并且更易于測驗的代碼,它們也為Spring中的各種模塊提供了基礎支持,
91. 解釋一下什么是 aop?
AOP(Aspect-Oriented Programming,面向方面編程),可以說是OOP(Object-Oriented Programing,面向物件編程)的補充和完善,OOP引入封裝、繼承和多型性等概念來建立一種物件層次結構,用以模擬公共行為的一個集合,當我們需要為分散的物件引入公共行為的時候,OOP則顯得無能為力,也就是說,OOP允許你定義從上到下的關系,但并不適合定義從左到右的關系,例如日志功能,日志代碼往往水平地散布在所有物件層次中,而與它所散布到的物件的核心功能毫無關系,對于其他型別的代碼,如安全性、例外處理和透明的持續性也是如此,這種散布在各處的無關的代碼被稱為橫切(cross-cutting)代碼,在OOP設計中,它導致了大量代碼的重復,而不利于各個模塊的重用,
而AOP技術則恰恰相反,它利用一種稱為“橫切”的技術,剖解開封裝的物件內部,并將那些影響了多個類的公共行為封裝到一個可重用模塊,并將其名為“Aspect”,即方面,所謂“方面”,簡單地說,就是將那些與業務無關,卻為業務模塊所共同呼叫的邏輯或責任封裝起來,便于減少系統的重復代碼,降低模塊間的耦合度,并有利于未來的可操作性和可維護性,AOP代表的是一個橫向的關系,如果說“物件”是一個空心的圓柱體,其中封裝的是物件的屬性和行為;那么面向方面編程的方法,就仿佛一把利刃,將這些空心圓柱體剖開,以獲得其內部的訊息,而剖開的切面,也就是所謂的“方面”了,然后它又以巧奪天功的妙手將這些剖開的切面復原,不留痕跡,
使用“橫切”技術,AOP把軟體系統分為兩個部分:核心關注點和橫切關注點,業務處理的主要流程是核心關注點,與之關系不大的部分是橫切關注點,橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處都基本相似,比如權限認證、日志、事務處理,Aop 的作用在于分離系統中的各種關注點,將核心關注點和橫切關注點分離開來,正如Avanade公司的高級方案構架師Adam Magee所說,AOP的核心思想就是“將應用程式中的商業邏輯同對其提供支持的通用服務進行分離,”
92. 解釋一下什么是 ioc?
IOC是Inversion of Control的縮寫,多數書籍翻譯成“控制反轉”,
1996年,Michael Mattson在一篇有關探討面向物件框架的文章中,首先提出了IOC 這個概念,對于面向物件設計及編程的基本思想,前面我們已經講了很多了,不再贅述,簡單來說就是把復雜系統分解成相互合作的物件,這些物件類通過封裝以后,內部實作對外部是透明的,從而降低了解決問題的復雜度,而且可以靈活地被重用和擴展,
IOC理論提出的觀點大體是這樣的:借助于“第三方”實作具有依賴關系的物件之間的解耦,如下圖:
大家看到了吧,由于引進了中間位置的“第三方”,也就是IOC容器,使得A、B、C、D這4個物件沒有了耦合關系,齒輪之間的傳動全部依靠“第三方”了,全部物件的控制權全部上繳給“第三方”IOC容器,所以,IOC容器成了整個系統的關鍵核心,它起到了一種類似“粘合劑”的作用,把系統中的所有物件粘合在一起發揮作用,如果沒有這個“粘合劑”,物件與物件之間會彼此失去聯系,這就是有人把IOC容器比喻成“粘合劑”的由來,
我們再來做個試驗:把上圖中間的IOC容器拿掉,然后再來看看這套系統:
我們現在看到的畫面,就是我們要實作整個系統所需要完成的全部內容,這時候,A、B、C、D這4個物件之間已經沒有了耦合關系,彼此毫無聯系,這樣的話,當你在實作A的時候,根本無須再去考慮B、C和D了,物件之間的依賴關系已經降低到了最低程度,所以,如果真能實作IOC容器,對于系統開發而言,這將是一件多么美好的事情,參與開發的每一成員只要實作自己的類就可以了,跟別人沒有任何關系!
我們再來看看,控制反轉(IOC)到底為什么要起這么個名字?我們來對比一下:
軟體系統在沒有引入IOC容器之前,如圖1所示,物件A依賴于物件B,那么物件A在初始化或者運行到某一點的時候,自己必須主動去創建物件B或者使用已經創建的物件B,無論是創建還是使用物件B,控制權都在自己手上,
軟體系統在引入IOC容器之后,這種情形就完全改變了,如圖3所示,由于IOC容器的加入,物件A與物件B之間失去了直接聯系,所以,當物件A運行到需要物件B的時候,IOC容器會主動創建一個物件B注入到物件A需要的地方,
通過前后的對比,我們不難看出來:物件A獲得依賴物件B的程序,由主動行為變為了被動行為,控制權顛倒過來了,這就是“控制反轉”這個名稱的由來,
93. spring 有哪些主要模塊?
Spring框架至今已集成了20多個模塊,這些模塊主要被分如下圖所示的核心容器、資料訪問/集成,、Web、AOP(面向切面編程)、工具、訊息和測驗模塊,
更多資訊:howtodoinjava.com/java-spring-framework-tutorials/
94. spring 常用的注入方式有哪些?
Spring通過DI(依賴注入)實作IOC(控制反轉),常用的注入方式主要有三種:
- 構造方法注入
- setter注入
- 基于注解的注入
95. spring 中的 bean 是執行緒安全的嗎?
Spring容器中的Bean是否執行緒安全,容器本身并沒有提供Bean的執行緒安全策略,因此可以說spring容器中的Bean本身不具備執行緒安全的特性,但是具體還是要結合具體scope的Bean去研究,
96. spring 支持幾種 bean 的作用域?
當通過spring容器創建一個Bean實體時,不僅可以完成Bean實體的實體化,還可以為Bean指定特定的作用域,Spring支持如下5種作用域:
- singleton:單例模式,在整個Spring IoC容器中,使用singleton定義的Bean將只有一個實體
- prototype:原型模式,每次通過容器的getBean方法獲取prototype定義的Bean時,都將產生一個新的Bean實體
- request:對于每次HTTP請求,使用request定義的Bean都將產生一個新實體,即每次HTTP請求將會產生不同的Bean實體,只有在Web應用中使用Spring時,該作用域才有效
- session:對于每次HTTP Session,使用session定義的Bean豆漿產生一個新實體,同樣只有在Web應用中使用Spring時,該作用域才有效
- globalsession:每個全域的HTTP Session,使用session定義的Bean都將產生一個新實體,典型情況下,僅在使用portlet context的時候有效,同樣只有在Web應用中使用Spring時,該作用域才有效
其中比較常用的是singleton和prototype兩種作用域,對于singleton作用域的Bean,每次請求該Bean都將獲得相同的實體,容器負責跟蹤Bean實體的狀態,負責維護Bean實體的生命周期行為;如果一個Bean被設定成prototype作用域,程式每次請求該id的Bean,Spring都會新建一個Bean實體,然后回傳給程式,在這種情況下,Spring容器僅僅使用new 關鍵字創建Bean實體,一旦創建成功,容器不在跟蹤實體,也不會維護Bean實體的狀態,
如果不指定Bean的作用域,Spring默認使用singleton作用域,Java在創建Java實體時,需要進行記憶體申請;銷毀實體時,需要完成垃圾回收,這些作業都會導致系統開銷的增加,因此,prototype作用域Bean的創建、銷毀代價比較大,而singleton作用域的Bean實體一旦創建成功,可以重復使用,因此,除非必要,否則盡量避免將Bean被設定成prototype作用域,
97. spring 自動裝配 bean 有哪些方式?
Spring容器負責創建應用程式中的bean同時通過ID來協調這些物件之間的關系,作為開發人員,我們需要告訴Spring要創建哪些bean并且如何將其裝配到一起,
spring中bean裝配有兩種方式:
- 隱式的bean發現機制和自動裝配
- 在java代碼或者XML中進行顯示配置
當然這些方式也可以配合使用,
98. spring 事務實作方式有哪些?
- 編程式事務管理對基于 POJO 的應用來說是唯一選擇,我們需要在代碼中呼叫beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是編程式事務管理,
- 基于 TransactionProxyFactoryBean 的宣告式事務管理
- 基于 @Transactional 的宣告式事務管理
- 基于 Aspectj AOP 配置事務
99. 說一下 spring 的事務隔離?
事務隔離級別指的是一個事務對資料的修改與另一個并行的事務的隔離程度,當多個事務同時訪問相同資料時,如果沒有采取必要的隔離機制,就可能發生以下問題:
- 臟讀:一個事務讀到另一個事務未提交的更新資料,
- 幻讀:例如第一個事務對一個表中的資料進行了修改,比如這種修改涉及到表中的“全部資料行”,同時,第二個事務也修改這個表中的資料,這種修改是向表中插入“一行新資料”,那么,以后就會發生操作第一個事務的用戶發現表中還存在沒有修改的資料行,就好象發生了幻覺一樣,
- 不可重復讀:比方說在同一個事務中先后執行兩條一模一樣的select陳述句,期間在此次事務中沒有執行過任何DDL陳述句,但先后得到的結果不一致,這就是不可重復讀,
100. 說一下 spring mvc 運行流程?
Spring MVC運行流程圖:
Spring運行流程描述:
1. 用戶向服務器發送請求,請求被Spring 前端控制Servelt DispatcherServlet捕獲;
2. DispatcherServlet對請求URL進行決議,得到請求資源識別符號(URI),然后根據該URI,呼叫HandlerMapping獲得該Handler配置的所有相關的物件(包括Handler物件以及Handler物件對應的攔截器),最后以HandlerExecutionChain物件的形式回傳;
3. DispatcherServlet 根據獲得的Handler,選擇一個合適的HandlerAdapter;(附注:如果成功獲得HandlerAdapter后,此時將開始執行攔截器的preHandler(...)方法)
4. 提取Request中的模型資料,填充Handler入參,開始執行Handler(Controller), 在填充Handler的入參程序中,根據你的配置,Spring將幫你做一些額外的作業:
- HttpMessageConveter: 將請求訊息(如Json、xml等資料)轉換成一個物件,將物件轉換為指定的回應資訊
- 資料轉換:對請求訊息進行資料轉換,如String轉換成Integer、Double等
- 資料根式化:對請求訊息進行資料格式化, 如將字串轉換成格式化數字或格式化日期等
- 資料驗證: 驗證資料的有效性(長度、格式等),驗證結果存盤到BindingResult或Error中
5. Handler執行完成后,向DispatcherServlet 回傳一個ModelAndView物件;
6. 根據回傳的ModelAndView,選擇一個適合的ViewResolver(必須是已經注冊到Spring容器中的ViewResolver)回傳給DispatcherServlet ;
7. ViewResolver 結合Model和View,來渲染視圖;
8. 將渲染結果回傳給客戶端,
101. spring mvc 有哪些組件?
Spring MVC的核心組件:
- DispatcherServlet:中央控制器,把請求給轉發到具體的控制類
- Controller:具體處理請求的控制器
- HandlerMapping:映射處理器,負責映射中央處理器轉發給controller時的映射策略
- ModelAndView:服務層回傳的資料和視圖層的封裝類
- ViewResolver:視圖決議器,決議具體的視圖
- Interceptors :攔截器,負責攔截我們定義的請求然后做處理作業
102. @RequestMapping 的作用是什么?
RequestMapping是一個用來處理請求地址映射的注解,可用于類或方法上,用于類上,表示類中的所有回應請求的方法都是以該地址作為父路徑,
RequestMapping注解有六個屬性,下面我們把她分成三類進行說明,
value, method:
- value:指定請求的實際地址,指定的地址可以是URI Template 模式(后面將會說明);
- method:指定請求的method型別, GET、POST、PUT、DELETE等;
consumes,produces
- consumes:指定處理請求的提交內容型別(Content-Type),例如application/json, text/html;
- produces:指定回傳的內容型別,僅當request請求頭中的(Accept)型別中包含該指定型別才回傳;
params,headers
- params: 指定request中必須包含某些引數值是,才讓該方法處理,
- headers:指定request中必須包含某些指定的header值,才能讓該方法處理請求,
103. @Autowired 的作用是什么?
《@Autowired用法詳解》
十一、Spring Boot / Spring Cloud
104. 什么是 spring boot?
在Spring框架這個大家族中,產生了很多衍生框架,比如 Spring、SpringMvc框架等,Spring的核心內容在于控制反轉(IOC)和依賴注入(DI),所謂控制反轉并非是一種技術,而是一種思想,在操作方面是指在spring組態檔中創建<bean>,依賴注入即為由spring容器為應用程式的某個物件提供資源,比如 參考物件、常量資料等,
SpringBoot是一個框架,一種全新的編程規范,他的產生簡化了框架的使用,所謂簡化是指簡化了Spring眾多框架中所需的大量且繁瑣的組態檔,所以 SpringBoot是一個服務于框架的框架,服務范圍是簡化組態檔,
105. 為什么要用 spring boot?
- Spring Boot使編碼變簡單
- Spring Boot使配置變簡單
- Spring Boot使部署變簡單
- Spring Boot使監控變簡單
- Spring的不足
106. spring boot 核心組態檔是什么?
Spring Boot提供了兩種常用的組態檔:
- properties檔案
- yml檔案
107. spring boot 組態檔有哪幾種型別?它們有什么區別?
Spring Boot提供了兩種常用的組態檔,分別是properties檔案和yml檔案,相對于properties檔案而言,yml檔案更年輕,也有很多的坑,可謂成也蕭何敗蕭何,yml通過空格來確定層級關系,使組態檔結構跟清晰,但也會因為微不足道的空格而破壞了層級關系,
108. spring boot 有哪些方式可以實作熱部署?
SpringBoot熱部署實作有兩種方式:
①. 使用spring loaded
在專案中添加如下代碼:
<build>
<plugins>
<plugin>
<!-- springBoot編譯插件-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<!-- spring熱部署 -->
<!-- 該依賴在此處下載不下來,可以放置在build標簽外部下載完成后再粘貼進plugin中 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.6.RELEASE</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
添加完畢后需要使用mvn指令運行:
首先找到IDEA中的Edit configurations ,然后進行如下操作:(點擊左上角的"+",然后選擇maven將出現右側面板,在紅色劃線部位輸入如圖所示指令,你可以為該指令命名(此處命名為MvnSpringBootRun))
點擊保存將會在IDEA專案運行部位出現,點擊綠色箭頭運行即可
②. 使用spring-boot-devtools
在專案的pom檔案中添加依賴:
<!--熱部署jar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
然后:使用 shift+ctrl+alt+"/" (IDEA中的快捷鍵) 選擇"Registry" 然后勾選 compiler.automake.allow.when.app.running
109. jpa 和 hibernate 有什么區別?
- JPA Java Persistence API,是Java EE 5的標準ORM介面,也是ejb3規范的一部分,
- Hibernate,當今很流行的ORM框架,是JPA的一個實作,但是其功能是JPA的超集,
- JPA和Hibernate之間的關系,可以簡單的理解為JPA是標準介面,Hibernate是實作,那么Hibernate是如何實作與JPA的這種關系的呢,Hibernate主要是通過三個組件來實作的,及hibernate-annotation、hibernate-entitymanager和hibernate-core,
- hibernate-annotation是Hibernate支持annotation方式配置的基礎,它包括了標準的JPA annotation以及Hibernate自身特殊功能的annotation,
- hibernate-core是Hibernate的核心實作,提供了Hibernate所有的核心功能,
- hibernate-entitymanager實作了標準的JPA,可以把它看成hibernate-core和JPA之間的配接器,它并不直接提供ORM的功能,而是對hibernate-core進行封裝,使得Hibernate符合JPA的規范,
110. 什么是 spring cloud?
從字面理解,Spring Cloud 就是致力于分布式系統、云服務的框架,
Spring Cloud 是整個 Spring 家族中新的成員,是最近云服務火爆的必然產物,
Spring Cloud 為開發人員提供了快速構建分布式系統中一些常見模式的工具,例如:
- 配置管理
- 服務注冊與發現
- 斷路器
- 智能路由
- 服務間呼叫
- 負載均衡
- 微代理
- 控制總線
- 一次性令牌
- 全域鎖
- 領導選舉
- 分布式會話
- 集群狀態
- 分布式訊息
- ……
使用 Spring Cloud 開發人員可以開箱即用的實作這些模式的服務和應用程式,這些服務可以任何環境下運行,包括分布式環境,也包括開發人員自己的筆記本電腦以及各種托管平臺,
111. spring cloud 斷路器的作用是什么?
在Spring Cloud中使用了Hystrix 來實作斷路器的功能,斷路器可以防止一個應用程式多次試圖執行一個操作,即很可能失敗,允許它繼續而不等待故障恢復或者浪費 CPU 周期,而它確定該故障是持久的,斷路器模式也使應用程式能夠檢測故障是否已經解決,如果問題似乎已經得到糾正,應用程式可以嘗試呼叫操作,
斷路器增加了穩定性和靈活性,以一個系統,提供穩定性,而系統從故障中恢復,并盡量減少此故障的對性能的影響,它可以幫助快速地拒絕對一個操作,即很可能失敗,而不是等待操作超時(或者不回傳)的請求,以保持系統的回應時間,如果斷路器提高每次改變狀態的時間的事件,該資訊可以被用來監測由斷路器保護系統的部件的健康狀況,或以提醒管理員當斷路器跳閘,以在打開狀態,
112. spring cloud 的核心組件有哪些?
①. 服務發現——Netflix Eureka
一個RESTful服務,用來定位運行在AWS地區(Region)中的中間層服務,由兩個組件組成:Eureka服務器和Eureka客戶端,Eureka服務器用作服務注冊服務器,Eureka客戶端是一個java客戶端,用來簡化與服務器的互動、作為輪詢負載均衡器,并提供服務的故障切換支持,Netflix在其生產環境中使用的是另外的客戶端,它提供基于流量、資源利用率以及出錯狀態的加權負載均衡,
②. 客服端負載均衡——Netflix Ribbon
Ribbon,主要提供客戶側的軟體負載均衡演算法,Ribbon客戶端組件提供一系列完善的配置選項,比如連接超時、重試、重試演算法等,Ribbon內置可插拔、可定制的負載均衡組件,
③. 斷路器——Netflix Hystrix
斷路器可以防止一個應用程式多次試圖執行一個操作,即很可能失敗,允許它繼續而不等待故障恢復或者浪費 CPU 周期,而它確定該故障是持久的,斷路器模式也使應用程式能夠檢測故障是否已經解決,如果問題似乎已經得到糾正,應用程式可以嘗試呼叫操作,
④. 服務網關——Netflix Zuul
類似nginx,反向代理的功能,不過netflix自己增加了一些配合其他組件的特性,
⑤. 分布式配置——Spring Cloud Config
這個還是靜態的,得配合Spring Cloud Bus實作動態的配置更新,
未完待續,不定時更新!
歡迎大家關注我的公眾號:Java團長,面試題更新之后可以在第一時間獲取~

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/233127.html
標籤:java
