Java 最常見的 100+ 面試題和答案:面試必備( 2021 年 4 月最新版,持續更新)
發現網上很多Java面試題都沒有答案,所以花了很長時間蒐集整理出來了這套Java面試題大全,希望對大家有幫助哈~
博主已將以下這些面試題整理成了一個Java面試手冊,是PDF版的。
專欄 限字數,需要pdf版的可以私信我,扣1領取
一、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
下面是測試程式碼:
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高。
Java面試題目錄
MyBatis面試篇
Zookeeper面試篇
Java基礎篇(45道面試題)
字串&&集合篇(74道)
併發程式設計篇(79道)
JVM篇(56道)
資料結構與演算法(56道)
MySQL篇(59道)
另外還附帶了 52條SQL最佳化策略 以及一千行SQL命令
Redis篇(48道)
Mongo篇(83道)
Spring篇(58道)
MyBatis篇(47道)
SpringBoot篇(43道)
常用註解篇
除此之外還有SpringCloud(31道)、Nginx(28道)、MQ(45道)、Dubbo(38道)、Kafka(12道)、ES(47道)、Linux(38道)等內容。。
這裡就不一一展示了
我已將上面這些面試題整理成了一個Java面試手冊,是PDF版的。
相關文章
- 2021-09-195歲女童因甲醛得病,專家提示:家長們不該疏忽這2個“訊號”
- 2021-09-08相親物件太極品?教你十招逃離現場!
- 2021-09-08“加水”就能辨別假酒?別坑了,這些才是真正的鑑別方法
- 2021-09-05老式縫紉機、老式早點鋪……寶山這位手藝人巧手還原“老辰光”!
- 2021-07-07GitHub上標星65k+超火的《Java大廠面試》,分享整理的PDF版本