Java必備知識總結(2020版)

一、Java基礎

1.== 和 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

equals 解讀

equals 源碼

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

equals 本質上就是 ==,只不過 String 和 Integer 等重寫了 equals 方法,把它變成了值比較。

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

不對,哈希值相等,並不一定能得出鍵值對相等

代碼示例:

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

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

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

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

String 不屬於基礎類型,

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

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

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

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

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

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

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

7. 如何將字符串反轉?

使用 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

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

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

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

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

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

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

二、容器

1. java 容器都有哪些?

2.Collection 和 Collections 有什麼區別?

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

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

4. HashMap 和 Hashtable 有什麼區別?

hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。
hashMap允許空鍵值,而hashTable不允許。

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

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

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

HashMap概述: HashMap是基於哈希表的Map接口的非同步實現。此實現提供所有可選的映射操作,並允許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。 

HashMap的數據結構: 在java編程語言中,最基本的結構就是兩種,一個是數組,另外一個是模擬指針(引用),所有的數據結構都可以用這兩個基本結構來構造的,HashMap也不例外。HashMap實際上是一個“鏈表散列”的數據結構,即數組和鏈表的結合體

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

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

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

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

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

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

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

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

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

Vector是同步的,而ArrayList不是。然而,如果你尋求在迭代的時候對列表進行改變,你應該使用CopyOnWriteArrayList。 
ArrayList更加通用,因爲我們可以使用Collections工具類輕易地獲取同步列表和只讀列表

CopyOnWriteArrayList

  • 實現了List接口
  • 內部持有一個ReentrantLock lock = new ReentrantLock();
  • 底層是用volatile transient聲明的數組 array
  • 讀寫分離,寫時複製出一個新的數組,完成插入、修改或者移除操作後將新數組賦值給array

參考:https://www.cnblogs.com/simple-focus/p/7439919.html

11. Array 和 ArrayList 有何區別?

Array可以容納基本類型和對象,而ArrayList只能容納對象。 
Array是指定大小的,而ArrayList大小是固定的。 
Array沒有提供ArrayList那麼多功能,比如addAll、removeAll和iterator等。

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

poll() 和 remove() 都是從隊列中取出一個元素,

但是 poll() 在獲取元素失敗的時候會返回空,但是 remove() 失敗的時候會拋出異常

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

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

14. 迭代器 Iterator 是什麼?

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

15. 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中插入和刪除元素。

16. Iterator 和 ListIterator 有什麼區別?

Iterator可用來遍歷Set和List集合,但是ListIterator只能用來遍歷List。 
Iterator對集合只能是前向遍歷,ListIterator既可以前向也可以後向。 
ListIterator實現了Iterator接口,幷包含其他的功能,比如:增加元素,替換元素,獲取前一個和後一個元素的索引,等等。

三、多線程

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

①. 繼承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()方法來獲得子線程執行結束後的返回值。

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

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

Runnable接口中的run()方法的返回值是void,它做的事情只是純粹地去執行run()方法中的代碼而已;
Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果。

3. 線程有哪些狀態?

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

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

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

sleep():方法是線程類(Thread)的靜態方法,讓調用線程進入睡眠狀態,讓出執行機會給其他線程,等到休眠時間結束後,線程進入就緒狀態和其他線程一起競爭cpu的執行時間。因爲sleep() 是static靜態的方法,他不能改變對象的機鎖,當一個synchronized塊中調用了sleep() 方法,線程雖然進入休眠,但是對象的機鎖沒有被釋放,其他線程依然無法訪問這個對象。

wait():wait()是Object類的方法,當一個線程執行到wait方法時,它就進入到一個和該對象相關的等待池,同時釋放對象的機鎖,使得其他線程能夠訪問,可以通過notify,notifyAll方法來喚醒等待的線程。

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

如果線程調用了對象的 wait()方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。
當有線程調用了對象的 notifyAll()方法(喚醒所有 wait 線程)或 notify()方法(只隨機喚醒一個 wait 線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。也就是說,調用了notify後只要一個線程會由等待池進入鎖池,而notifyAll會將該對象等待池內的所有線程移動到鎖池中,等待鎖競爭。
優先級高的線程競爭到對象鎖的概率大,假若某線程沒有競爭到該對象鎖,它還會留在鎖池中,唯有線程再次調用 wait()方法,它纔會重新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,直到執行完了 synchronized 代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。

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

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

start()方法來啓動一個線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,可以直接繼續執行下面的代碼; 這時此線程是處於就緒狀態, 並沒有運行。 然後通過此Thread類調用方法run()來完成其運行狀態, 這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。然後CPU再調度其它線程。

run()方法是在本線程裏的,只是線程裏的一個函數,而不是多線程的。 如果直接調用run(),其實就相當於是調用了一個普通函數而已,直接待用run()方法必須等待run()方法執行完畢才能執行下面的代碼,所以執行路徑還是隻有一條,根本就沒有線程的特徵,所以在多線程執行時要使用start()方法而不是run()方法。

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

①. newFixedThreadPool(int nThreads)

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

②. newCachedThreadPool()

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

③. newSingleThreadExecutor()

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

④. newScheduledThreadPool(int corePoolSize)

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

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

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

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

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

1 接收的參數不一樣
2 submit有返回值,而execute沒有
3 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++一樣的指針越界到其他進程的問題。
 

 

 

 

--------------------EOF

參考:https://blog.csdn.net/qq_41701956/article/details/103253168

 

 

 

 

 

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