1. 程式人生 > >阿里p8架構師分享:50道2018年最經典的面試題,(含標準答案!)

阿里p8架構師分享:50道2018年最經典的面試題,(含標準答案!)

(一小部分題的答案被我略作改動)

1、什麼是執行緒區域性變數?

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

2.用 wait-notify 寫一段程式碼來解決生產者-消費者問題?

請參考答案中的示例程式碼。只要記住在同步塊中呼叫 wait() 和 notify()方法,如果阻塞,通過迴圈來測試等待條件。

【生產者】

package com.edu.chapter03.test;

import java.util.Vector;

import java.util.logging.Level;

import java.util.logging.Logger;

public class Producer implements Runnable {

private final Vector sharedQueue;

private final int SIZE;

public Producer(Vector sharedQueue, int size) {

this.sharedQueue = sharedQueue;

this.SIZE = size;

}

@Override

public void run() {

// TODO Auto-generated method stub

for (int i = 0; i < 7; i++) {

System.out.println("Produced:" + i);

try {

produce(i);

} catch (InterruptedException ex) {

Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);

}

}

}

private void produce(int i) throws InterruptedException {

//wait if queue is full

while (sharedQueue.size() == SIZE) {

synchronized (sharedQueue) {

System.out.println("Queue is full " + Thread.currentThread().getName()

+ " is waiting , size: " + sharedQueue.size());

sharedQueue.wait();

}

}

//producing element and notify consumers

synchronized (sharedQueue) {

sharedQueue.add(i);

sharedQueue.notifyAll();

}

}

}

【消費者】

package com.edu.chapter03.test;

import java.util.Vector;

import java.util.logging.Level;

import java.util.logging.Logger;

public class Consumer implements Runnable {

private final Vector sharedQueue;

private final int SIZE;

public Consumer(Vector sharedQueue, int size) {

this.sharedQueue = sharedQueue;

this.SIZE = size;

}

@Override

public void run() {

// TODO Auto-generated method stub

while (true) {

try {

System.out.println("Consumer: " + consume());

Thread.sleep(50);

} catch (InterruptedException ex) {

Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);

}

}

}

private int consume() throws InterruptedException {

//wait if queue is empty

while (sharedQueue.isEmpty()) {

synchronized (sharedQueue) {

System.out.println("Queue is empty " + Thread.currentThread().getName()

+ " is waiting , size: " + sharedQueue.size());

sharedQueue.wait();

}

}

//otherwise consume element and notify waiting producer

synchronized (sharedQueue) {

sharedQueue.notifyAll();

return (Integer) sharedQueue.remove(0);

}

}

}

【測試函式】

package com.edu.chapter03.test;

import java.util.Vector;

public class ProducerConsumerSolution {

public static void main(String[] args) {

Vector sharedQueue = new Vector();

int size = 4;

Thread prodThread = new Thread(new Producer(sharedQueue, size), "Producer");

Thread consThread = new Thread(new Consumer(sharedQueue, size), "Consumer");

prodThread.start();

consThread.start();

}

}

3. 用 Java 寫一個執行緒安全的單例模式(Singleton)?

請參考答案中的示例程式碼,這裡面一步一步教你建立一個執行緒安全的 Java 單例類。當我們說執行緒安全時,意思是即使初始化是在多執行緒環境中,仍然能保證單個例項。Java 中,使用列舉作為單例類是最簡單的方式來建立執行緒安全單例模式的方式。

立即載入/餓漢式:

【在呼叫方法前,例項就已經被建立】

package com.weishiyao.learn.day8.singleton.ep1;

public class MyObject {

// 立即載入方式==惡漢模式

private static MyObject myObject = new MyObject();

private MyObject() {

}

public static MyObject getInstance() {

// 此程式碼版本為立即載入

// 此版本程式碼的缺點是不能有其他例項變數

// 因為getInstance()方法沒有同步

// 所以有可能出現非執行緒安全的問題

return myObject;

}

}

【建立執行緒類】

package com.weishiyao.learn.day8.singleton.ep1;

public class MyThread extends Thread {

@Override

public void run() {

System.out.println(MyObject.getInstance().hashCode());

}

}

【建立執行類】

package com.weishiyao.learn.day8.singleton.ep1;

public class Run {

public static void main(String[] args) {

MyThread t1 = new MyThread();

MyThread t2 = new MyThread();

MyThread t3 = new MyThread();

t1.start();

t2.start();

t3.start();

}

}

延遲載入/懶漢式

【建物件的例項】

package com.weishiyao.learn.day8.singleton.ep2;

public class MyObject {

private static MyObject myObject;

private MyObject() {

}

public static MyObject getInstance() {

// 延遲載入

if (myObject != null) {

} else {

myObject = new MyObject();

}

return myObject;

}

}

【建立執行緒類】

package com.weishiyao.learn.day8.singleton.ep2;

public class MyThread extends Thread {

@Override

public void run() {

System.out.println(MyObject.getInstance().hashCode());

}

}

【建立執行類】

package com.weishiyao.learn.day8.singleton.ep2;

public class Run {

public static void main(String[] args) {

MyThread t1 = new MyThread();

t1.start();

}

}

【執行測試類】

package com.weishiyao.learn.day8.singleton.ep2;

public class Run {

public static void main(String[] args) {

MyThread t1 = new MyThread();

MyThread t2 = new MyThread();

MyThread t3 = new MyThread();

MyThread t4 = new MyThread();

MyThread t5 = new MyThread();

t1.start();

t2.start();

t3.start();

t4.start();

t5.start();

}

}

4.Java 中 sleep 方法和 wait 方法的區別?

雖然兩者都是用來暫停當前執行的執行緒,但是 sleep() 實際上只是短暫停頓,因為它不會釋放鎖,而 wait() 意味著條件等待,這就是為什麼該方法要釋放鎖,因為只有這樣,其他等待的執行緒才能在滿足條件時獲取到該鎖。

5.什麼是不可變物件(immutable object)?Java 中怎麼建立一個不可變物件?

不可變物件指物件一旦被建立,狀態就不能再改變。任何修改都會建立一個新的物件,如 String、Integer及其它包裝類。詳情參見答案,一步一步指導你在 Java 中建立一個不可變的類。

6.我們能建立一個包含可變物件的不可變物件嗎?

是的,我們是可以建立一個包含可變物件的不可變物件的,你只需要謹慎一點,不要共享可變物件的引用就可以了,如果需要變化時,就返回原物件的一個拷貝。最常見的例子就是物件中包含一個日期物件的引用。

7.Java 中應該使用什麼資料型別來代表價格?

如果不是特別關心記憶體和效能的話,使用BigDecimal,否則使用預定義精度的 double 型別。

8.怎麼將 byte 轉換為 String?

可以使用 String 接收 byte[] 引數的構造器來進行轉換,需要注意的點是要使用的正確的編碼,否則會使用平臺預設編碼,這個編碼可能跟原來的編碼相同,也可能不同。

9.Java 中 bytes 與其他型別的轉換?

public class Test {

private static ByteBuffer buffer = ByteBuffer.allocate(8);

public static void main(String[] args) {

//測試 int 轉 byte

int int0 = 234;

byte byte0 = intToByte(int0);

System.out.println("byte0=" + byte0);//byte0=-22

//測試 byte 轉 int

int int1 = byteToInt(byte0);

System.out.println("int1=" + int1);//int1=234

//測試 int 轉 byte 陣列

int int2 = 1417;

byte[] bytesInt = intToByteArray(int2);

System.out.println("bytesInt=" + bytesInt);//bytesInt=[[email protected]

//測試 byte 陣列轉 int

int int3 = byteArrayToInt(bytesInt);

System.out.println("int3=" + int3);//int3=1417

//測試 long 轉 byte 陣列

long long1 = 2223;

byte[] bytesLong = longToBytes(long1);

System.out.println("bytes=" + bytesLong);//bytes=[[email protected]

//測試 byte 陣列 轉 long

long long2 = bytesToLong(bytesLong);

System.out.println("long2=" + long2);//long2=2223

}

//byte 與 int 的相互轉換

public static byte intToByte(int x) {

return (byte) x;

}

public static int byteToInt(byte b) {

//Java 總是把 byte 當做有符處理;我們可以通過將其和 0xFF 進行二進位制與得到它的無符值

return b & 0xFF;

}

//byte 陣列與 int 的相互轉換

public static int byteArrayToInt(byte[] b) {

return b[3] & 0xFF |

(b[2] & 0xFF) << 8 |

(b[1] & 0xFF) << 16 |

(b[0] & 0xFF) << 24;

}

public static byte[] intToByteArray(int a) {

return new byte[] {

(byte) ((a >> 24) & 0xFF),

(byte) ((a >> 16) & 0xFF),

(byte) ((a >> 8) & 0xFF),

(byte) (a & 0xFF)

};

}

//byte 陣列與 long 的相互轉換

public static byte[] longToBytes(long x) {

buffer.putLong(0, x);

return buffer.array();

}

public static long bytesToLong(byte[] bytes) {

buffer.put(bytes, 0, bytes.length);

buffer.flip();//need flip

return buffer.getLong();

}

}

10.我們能將 int 強制轉換為 byte 型別的變數嗎?如果該值大於 byte 型別的範圍,將會出現什麼現象?

是的,我們可以做強制轉換,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,所以,如果強制轉化是,int 型別的高 24 位將會被丟棄,byte 型別的範圍是從 -128 到 128。

11.存在兩個類,B 繼承 A,C 繼承 B,我們能將 B 轉換為 C 麼?如 C = (C) B;

可以,向下轉型。但是不建議使用,容易出現型別轉型異常。

12.哪個類包含 clone 方法?是 Cloneable 還是 Object?

java.lang.Cloneable 是一個標示性介面,不包含任何方法,clone 方法在 object 類中定義。並且需要知道 clone() 方法是一個本地方法,這意味著它是由 c 或 c++ 或 其他本地語言實現的。

13.Java 中 ++ 操作符是執行緒安全的嗎?

不是執行緒安全的操作。它涉及到多個指令,如讀取變數值,增加,然後儲存回記憶體,這個過程可能會出現多個執行緒交差。

14.a = a + b 與 a += b 的區別

+= 隱式的將加操作的結果型別強制轉換為持有結果的型別。如果兩這個整型相加,如 byte、short 或者 int,首先會將它們提升到 int 型別,然後在執行加法操作。如果加法操作的結果比 a 的最大值要大,則 a+b 會出現編譯錯誤,但是 a += b 沒問題,如下:

byte a = 127;

byte b = 127;

b = a + b; // error : cannot convert from int to byte

b += a; // ok

(譯者注:這個地方應該表述的有誤,其實無論 a+b 的值為多少,編譯器都會報錯,因為 a+b 操作會將 a、b 提升為 int 型別,所以將 int 型別賦值給 byte 就會編譯出錯)

15.我能在不進行強制轉換的情況下將一個 double 值賦值給 long 型別的變數嗎?

說到這裡,也給大家推薦一個架構交流學習群:828545509,裡面會分享一些資深架構師錄製的視訊錄影:有Spring,MyBatis,Netty原始碼分析
,高併發、高效能、分散式、微服務架構的原理,JVM效能優化這些成為架構師必備的知識體系。還能領取免費的學習資源,相信對於已經工作
和遇到技術瓶頸的碼友,在這個群裡會有你需要的內容。

點選連結加入群聊【Java高階架構師學習群】:https://jq.qq.com/?_wv=1027&k=5T2kMGl

不行,你不能在沒有強制型別轉換的前提下將一個 double 值賦值給 long 型別的變數,因為 double 型別的範圍比 long 型別更廣,所以必須要進行強制轉換。

16. 3*0.1 == 0.3 將會返回什麼?true 還是 false?

false,因為有些浮點數不能完全精確的表示出來。

17.int 和 Integer 哪個會佔用更多的記憶體?

Integer 物件會佔用更多的記憶體。Integer 是一個物件,需要儲存物件的元資料。但是 int 是一個原始型別的資料,所以佔用的空間更少。

18.為什麼 Java 中的 String 是不可變的(Immutable)?

Java 中的 String 不可變是因為 Java 的設計者認為字串使用非常頻繁,將字串設定為不可變可以允許多個客戶端之間共享相同的字串。更詳細的內容參見答案。

19.我們能在 Switch 中使用 String 嗎?

從 Java 7 開始,我們可以在 switch case 中使用字串,但這僅僅是一個語法糖。內部實現在 switch 中使用字串的 hash code。

20.Java 中的構造器鏈是什麼?

當你從一個構造器中呼叫另一個構造器,就是Java 中的構造器鏈。這種情況只在過載了類的構造器的時候才會出現。

21.64 位 JVM 中,int 的長度是多數?

Java 中,int 型別變數的長度是一個固定值,與平臺無關,都是 32 位。意思就是說,在 32 位 和 64 位 的Java 虛擬機器中,int 型別的長度是相同的。

22.Serial 與 Parallel GC之間的不同之處?

Serial 與 Parallel 在GC執行的時候都會引起 stop-the-world。它們之間主要不同 serial 收集器是預設的複製收集器,執行 GC 的時候只有一個執行緒,而 parallel 收集器使用多個 GC 執行緒來執行。

23. 32 位和 64 位的 JVM,int 型別變數的長度是多數?

32 位和 64 位的 JVM 中,int 型別變數的長度是相同的,都是 32 位或者 4 個位元組。

24.Java 中 WeakReference 與 SoftReference的區別?

雖然 WeakReference 與 SoftReference 都有利於提高 GC 和 記憶體的效率,但是 WeakReference ,一旦失去最後一個強引用,就會被 GC 回收,而軟引用雖然不能阻止被回收,但是可以延遲到 JVM 記憶體不足的時候。

25.WeakHashMap 是怎麼工作的?

WeakHashMap 的工作與正常的 HashMap 類似,但是使用弱引用作為 key,意思就是當 key 物件沒有任何引用時,key/value 將會被回收。

26.JVM 選項 -XX:+UseCompressedOops 有什麼作用?為什麼要使用?

當你將你的應用從 32 位的 JVM 遷移到 64 位的 JVM 時,由於物件的指標從 32 位增加到了 64 位,因此堆記憶體會突然增加,差不多要翻倍。這也會對 CPU 快取(容量比記憶體小很多)的資料產生不利的影響。因為,遷移到 64 位的 JVM 主要動機在於可以指定最大堆大小,通過壓縮 OOP 可以節省一定的記憶體。通過 -XX:+UseCompressedOops 選項,JVM 會使用 32 位的 OOP,而不是 64 位的 OOP。

27.怎樣通過 Java 程式來判斷 JVM 是 32 位 還是 64 位?

你可以檢查某些系統屬性如 sun.arch.data.model 或 os.arch 來獲取該資訊。

28.32 位 JVM 和 64 位 JVM 的最大堆記憶體分別是多數?

理論上說上 32 位的 JVM 堆記憶體可以到達 2^32,即 4GB,但實際上會比這個小很多。不同作業系統之間不同,如 Windows 系統大約 1.5 GB,Solaris 大約 3GB。64 位 JVM允許指定最大的堆記憶體,理論上可以達到 2^64,這是一個非常大的數字,實際上你可以指定堆記憶體大小到 100GB。甚至有的 JVM,如 Azul,堆記憶體到 1000G 都是可能的。

29.JRE、JDK、JVM 及 JIT 之間有什麼不同?

JRE 代表 Java 執行時(Java run-time),是執行 Java 引用所必須的。JDK 代表 Java 開發工具(Java development kit),是 Java 程式的開發工具,如 Java 編譯器,它也包含 JRE。JVM 代表 Java 虛擬機器(Java virtual machine),它的責任是執行 Java 應用。JIT 代表即時編譯(Just In Time compilation),當代碼執行的次數超過一定的閾值時,會將 Java 位元組碼轉換為原生代碼,如,主要的熱點程式碼會被準換為原生代碼,這樣有利大幅度提高 Java 應用的效能。

30.解釋 Java 堆空間及 GC?

當通過 Java 命令啟動 Java 程序的時候,會為它分配記憶體。記憶體的一部分用於建立堆空間,當程式中建立物件的時候,就從對空間中分配記憶體。GC 是 JVM 內部的一個程序,回收無效物件的記憶體用於將來的分配。

31.你能保證 GC 執行嗎?

不能,雖然你可以呼叫 System.gc() 或者 Runtime.gc(),但是沒有辦法保證 GC 的執行。

32.怎麼獲取 Java 程式使用的記憶體?堆使用的百分比?

可以通過 java.lang.Runtime 類中與記憶體相關方法來獲取剩餘的記憶體,總記憶體及最大堆記憶體。通過這些方法你也可以獲取到堆使用的百分比及堆記憶體的剩餘空間。

Runtime.freeMemory() 方法返回剩餘空間的位元組數,Runtime.totalMemory() 方法總記憶體的位元組數,Runtime.maxMemory() 返回最大記憶體的位元組數。

33.Java 中堆和棧有什麼區別?(答案)

JVM 中堆和棧屬於不同的記憶體區域,使用目的也不同。棧常用於儲存方法幀和區域性變數,而物件總是在堆上分配。棧通常都比堆小,也不會在多個執行緒之間共享,而堆被整個 JVM 的所有執行緒共享。

34. “a==b”和”a.equals(b)”有什麼區別?

如果 a 和 b 都是物件,則 a==b 是比較兩個物件的引用,只有當 a 和 b 指向的是堆中的同一個物件才會返回 true,而 a.equals(b) 是進行邏輯比較,所以通常需要重寫該方法來提供邏輯一致性的比較。例如,String 類重寫 equals() 方法,所以可以用於兩個不同物件,但是包含的字母相同的比較。

35.a.hashCode() 有什麼用?與 a.equals(b) 有什麼關係?

hashCode() 方法是相應物件整型的 hash 值。它常用於基於 hash 的集合類,如 Hashtable、HashMap、LinkedHashMap等等。它與 equals() 方法關係特別緊密。根據 Java 規範,兩個使用 equal() 方法來判斷相等的物件,必須具有相同的 hash code。

36.final、finalize 和 finally 的不同之處?

final 是一個修飾符,可以修飾變數、方法和類。如果 final 修飾變數,意味著該變數的值在初始化後不能被改變。finalize 方法是在物件被回收之前呼叫的方法,給物件自己最後一個復活的機會,但是什麼時候呼叫 finalize 沒有保證。finally 是一個關鍵字,與 try 和 catch 一起用於異常的處理。finally 塊一定會被執行,無論在 try 塊中是否有發生異常。

37.Java 中的編譯期常量是什麼?使用它又什麼風險?

公共靜態不可變(public static final )變數也就是我們所說的編譯期常量,這裡的 public 可選的。實際上這些變數在編譯時會被替換掉,因為編譯器知道這些變數的值,並且知道這些變數在執行時不能改變。這種方式存在的一個問題是你使用了一個內部的或第三方庫中的公有編譯時常量,但是這個值後面被其他人改變了,但是你的客戶端仍然在使用老的值,甚至你已經部署了一個新的jar。為了避免這種情況,當你在更新依賴 JAR 檔案時,確保重新編譯你的程式。

38.List、Set、Map 和 Queue 之間的區別

List 是一個有序集合,允許元素重複。它的某些實現可以提供基於下標值的常量訪問時間,但是這不是 List 介面保證的。Set 是一個無序集合。

39.poll() 方法和 remove() 方法的區別?

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

40.Java 中 LinkedHashMap 和 PriorityQueue 的區別是什麼?

PriorityQueue 保證最高或者最低優先順序的的元素總是在佇列頭部,但是 LinkedHashMap 維持的順序是元素插入的順序。當遍歷一個 PriorityQueue 時,沒有任何順序保證,但是 LinkedHashMap 課保證遍歷順序是元素插入的順序。

41.ArrayList 與 LinkedList 的不區別?

最明顯的區別是 ArrrayList 底層的資料結構是陣列,支援隨機訪問,而 LinkedList 的底層資料結構書連結串列,不支援隨機訪問。使用下標訪問一個元素,ArrayList 的時間複雜度是 O(1),而 LinkedList 是 O(n)。更多細節的討論參見答案。

42.用哪兩種方式來實現集合的排序?

你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有順序的的集合,如 list,然後通過 Collections.sort() 來排序。

43.Java 中怎麼列印陣列?(answe

你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法來列印陣列。由於陣列沒有實現 toString() 方法,所以如果將陣列傳遞給 System.out.println() 方法,將無法打印出陣列的內容,但是 Arrays.toString() 可以列印每個元素。

44.Java 中的 LinkedList 是單向連結串列還是雙向連結串列?

是雙向連結串列,你可以檢查 JDK 的原始碼。在 Eclipse,你可以使用快捷鍵 Ctrl + T,直接在編輯器中開啟該類。

45.Java 中的 TreeMap 是採用什麼樹實現的?

Java 中的 TreeMap 是使用紅黑樹實現的。

46. Hashtable 與 HashMap 有什麼不同之處?

這兩個類有許多不同的地方,下面列出了一部分:

a) Hashtable 是 JDK 1 遺留下來的類,而 HashMap 是後來增加的。

b)Hashtable 是同步的,比較慢,但 HashMap 沒有同步策略,所以會更快。

c)Hashtable 不允許有個空的 key,但是 HashMap 允許出現一個 null key。

更多的不同之處參見答案。

47.Java 中的 HashSet,內部是如何工作的?

HashSet 的內部採用 HashMap來實現。由於 Map 需要 key 和 value,所以所有 key 的都有一個預設 value。類似於 HashMap,HashSet 不允許重複的 key,只允許有一個null key,意思就是 HashSet 中只允許儲存一個 null 物件。

48.寫一段程式碼在遍歷 ArrayList 時移除一個元素?

該問題的關鍵在於面試者使用的是 ArrayList 的 remove() 還是 Iterator 的 remove()方法。這有一段示例程式碼,是使用正確的方式來實現在遍歷的過程中移除元素,而不會出現 ConcurrentModificationException 異常的示例程式碼。

49.我們能自己寫一個容器類,然後使用 for-each 迴圈碼?

可以,你可以寫一個自己的容器類。如果你想使用 Java 中增強的迴圈來遍歷,你只需要實現 Iterable 介面。如果你實現 Collection 介面,預設就具有該屬性。

50.ArrayList 和 HashMap 的預設大小是多數?

在 Java 7 中,ArrayList 的預設大小是 10 個元素,HashMap 的預設大小是16個元素(必須是2的冪)。這就是 Java 7 中 ArrayList 和 HashMap 類的程式碼片段:

// from ArrayList.java JDK 1.7

private static final int DEFAULT_CAPACITY = 10;

//from HashMap.java JDK 7

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16