常見Java面試題解析(基礎篇,附答案)

前言

金三銀四馬上要來了,整理了Java一些經典面試題,也給出了答案,希望對大家有幫助,有哪裏你覺得不正確的話,歡迎指出,非常感謝。

HashMap,HashTable,ConcurrentHash的共同點和區別

思路:可以從它們的底層結構、是否允許存儲null,是否線性安全等幾個維度進行描述,最後可以向面試官描述一下HashMap的死循環問題,以及ConcurrentHashMap爲啥放棄分段鎖。

HashMap

  • 底層由鏈表+數組實現
  • 可以存儲null鍵和null值
  • 線性不安全
  • 初始容量爲16,擴容每次都是2的n次冪
  • 加載因子爲0.75,當Map中元素總數超過Entry數組的0.75,觸發擴容操作.
  • 併發情況下,HashMap進行put操作會引起死循環,導致CPU利用率接近100%

有關於HashMap死循環,有興趣可以看看這篇文章,寫得很好: 老生常談,HashMap的死循環

有關於HashMap這些常量設計目的,也可以看我這篇文章:
面試加分項-HashMap源碼中這些常量的設計目的

HashTable

  • HashTable的底層也是由鏈表+數組實現。
  • 無論key還是value都不能爲null
  • 它是線性安全的,使用了synchronized關鍵字。

ConcurrentHashMap

  • ConcurrentHashMap的底層是數組+鏈表/紅黑樹
  • 不能存儲null鍵和值
  • ConcurrentHashMap是線程安全的
  • ConcurrentHashMap使用鎖分段技術確保線性安全
  • JDK8爲何又放棄分段鎖,是因爲多個分段鎖浪費內存空間,競爭同一個鎖的概率非常小,分段鎖反而會造成效率低。

ArrayList和LinkedList有什麼區別。

思路:從它們的底層數據結構、效率、開銷進行闡述

  • ArrayList是數組的數據結構,LinkedList是鏈表的數據結構。
  • 隨機訪問的時候,ArrayList的效率比較高,因爲LinkedList要移動指針,而ArrayList是基於索引(index)的數據結構,可以直接映射到。
  • 插入、刪除數據時,LinkedList的效率比較高,因爲ArrayList要移動數據。
  • LinkedList比ArrayList開銷更大,因爲LinkedList的節點除了存儲數據,還需要存儲引用。

String,Stringbuffer,StringBuilder的區別。

String:

  • String類是一個不可變的類,一旦創建就不可以修改。
  • String是final類,不能被繼承
  • String實現了equals()方法和hashCode()方法

StringBuffer:

  • 繼承自AbstractStringBuilder,是可變類。
  • StringBuffer是線程安全的
  • 可以通過append方法動態構造數據。

StringBuilder:

  • 繼承自AbstractStringBuilder,是可變類。
  • StringBuilder是非線性安全的。
  • 執行效率比StringBuffer高。

JAVA中的幾種基本數據類型是什麼,各自佔用多少字節。

基本類型 位數 字節
int 32 4
short 16 2
long 64 8
byte 8 1
char 16 2
float 32 4
double 64 8
boolean 1 1/8

看例子:

public class Test {
    public static void main(String[] args) {
        System.out.println("Byte bit num: " + Byte.SIZE);
        System.out.println("Short bit num : " + Short.SIZE);
        System.out.println("Character bit num: " + Character.SIZE);
        System.out.println("Integer bit num: " + Integer.SIZE);
        System.out.println("Float bit num: " + Float.SIZE);
        System.out.println("Long bit num: " + Long.SIZE);
        System.out.println("Double bit num: " + Double.SIZE);
    }
}

運行結果:

Byte bit num: 8
Short bit num : 16
Character bit num: 16
Integer bit num: 32
Float bit num: 32
Long bit num: 64
Double bit num: 64

String s 與new String的區別

String str ="whx";
String newStr =new String ("whx");

String str ="whx"

先在常量池中查找有沒有"whx" 這個對象,如果有,就讓str指向那個"whx".如果沒有,在常量池中新建一個“whx”對象,並讓str指向在常量池中新建的對象"whx"。

String newStr =new String (“whx”);

是在堆中建立的對象"whx" ,在棧中創建堆中"whx" 對象的內存地址。

如圖所示:

這篇文章講的挺好的:
String和New String()的區別

Bio、Nio、Aio區別

BIO

就是傳統的 java.io 包,它是基於流模型實現的,交互的方式是同步、阻塞方式,也就是說在讀入輸入流或者輸出流時,在讀寫動作完成之前,線程會一直阻塞在那裏,它們之間的調用時可靠的線性順序。它的有點就是代碼比較簡單、直觀;缺點就是 IO 的效率和擴展性很低,容易成爲應用性能瓶頸。

NIO

是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以構建多路複用的、同步非阻塞 IO 程序,同時提供了更接近操作系統底層高性能的數據操作方式。

AIO

是 Java 1.7 之後引入的包,是 NIO 的升級版本,提供了異步非堵塞的 IO 操作方式,所以人們叫它 AIO(Asynchronous IO),異步 IO 是基於事件和回調機制實現的,也就是應用操作之後會直接返回,不會堵塞在那裏,當後臺處理完成,操作系統會通知相應的線程進行後續的操作。

以上內容來自這篇文章,大家可以看一下,寫得比較詳細

Java核心(五)深入理解BIO、NIO、AIO

談談spring的生命週期

  • 首先容器啓動後,對bean進行初始化
  • 按照bean的定義,注入屬性
  • 檢測該對象是否實現了xxxAware接口,並將相關的xxxAware實例注入給bean,如BeanNameAware等
  • 以上步驟,bean對象已正確構造,通過實現BeanPostProcessor接口,可以再進行一些自定義方法處理。
    如:postProcessBeforeInitialzation。
  • BeanPostProcessor的前置處理完成後,可以實現postConstruct,afterPropertiesSet,init-method等方法,
    增加我們自定義的邏輯,
  • 通過實現BeanPostProcessor接口,進行postProcessAfterInitialzation後置處理
  • 接着Bean準備好被使用啦。
  • 容器關閉後,如果Bean實現了DisposableBean接口,則會回調該接口的destroy()方法
  • 通過給destroy-method指定函數,就可以在bean銷燬前執行指定的邏

反射的原理,反射創建類實例的三種方式是什麼。

Java反射機制:

Java 的反射機制是指在運行狀態中,對於任意一個類都能夠知道這個類所有的屬性和方法; 並且對於任意一個對象,都能夠調用它的任意一個方法;這種動態獲取信息以及動態調用對象方法的功能成爲Java語言的反射機制

獲取 Class 類對象三種方式:

  • 使用 Class.forName 靜態方法
  • 使用類的.class 方法
  • 使用實例對象的 getClass() 方法

可以看一下我寫的這篇文章:
談談Java反射:從入門到實踐,再到原理

說幾種實現冪等的方式

什麼是冪等性?一次和多次請求某一個資源對於資源本身應該具有同樣的結果。就是說,其任意多次執行對資源本身所產生的影響均與一次執行的影響相同。

實現冪等一般有以下幾種方式:

  • 悲觀鎖方式(如數據庫的悲觀鎖,select…for update)
  • 樂觀鎖方式 (如CAS算法)
  • 唯一性約束(如唯一索引)
  • 分佈式鎖 (redis分佈式鎖等)

可以看一下這篇文章,寫得不錯:
探討一下實現冪等性的幾種方式

講講類的實例化順序,如父類靜態數據,構造函數,字段,子類靜態數據,構造函數,字段等。

直接看個例子吧:

public class Parent {
    {
        System.out.println("父類非靜態代碼塊");
    }
    static {
        System.out.println("父類靜態塊");
    }
    public Parent() {
        System.out.println("父類構造器");
    }
}
public class Son extends Parent {
    public Son() {
        System.out.println("子類構造器");
    }
    static {
        System.out.println("子類靜態代碼塊");
    }
    {
        System.out.println("子類非靜態代碼塊");
    }
}
public class Test {
    public static void main(String[] args) {
        Son son = new Son();
    }
}

運行結果:

父類靜態塊
子類靜態代碼塊
父類非靜態代碼塊
父類構造器
子類非靜態代碼塊
子類構造器

所以,類實例化順序爲:
父類靜態代碼塊/靜態域->子類靜態代碼塊/靜態域 -> 父類非靜態代碼塊 -> 父類構造器 -> 子類非靜態代碼塊 -> 子類構造器

反射中,Class.forName和ClassLoader區別

Class.forName和ClassLoader都可以對類進行加載。它們區別在哪裏呢?
ClassLoader負責加載 Java 類的字節代碼到 Java 虛擬機中。Class.forName其實是調用了ClassLoader,如下:


這裏面,forName0的第二個參數爲true,表示對加載的類進行初始化化。其實還可以調用Class<?> forName(String name, boolean initialize, ClassLoader loader)方法實現一樣的功能,它的源碼如下:

所以,Class.forName和ClassLoader的區別,就是在類加載的時候,class.forName有參數控制是否對類進行初始化。

JDK動態代理與cglib實現的區別

  • java動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。
  • cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。
  • JDK動態代理只能對實現了接口的類生成代理,而不能針對類
  • cglib是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法。因爲是繼承,所以該類或方法最好不要聲明成final

這篇文章寫得不錯,描述Java動態代理的幾種實現方式,分別說出相應的優缺點

error和exception的區別,CheckedException,RuntimeException的區別。

Error: 表示編譯時或者系統錯誤,如虛擬機相關的錯誤,OutOfMemoryError等,error是無法處理的。

Exception: 代碼異常,Java程序員關心的基類型通常是Exception。它能被程序本身可以處理,這也是它跟Error的區別。

它可以分爲RuntimeException(運行時異常)和CheckedException(可檢查的異常)。
常見的RuntimeException異常:

- NullPointerException 空指針異常
- ArithmeticException 出現異常的運算條件時,拋出此異常
- IndexOutOfBoundsException 數組索引越界異常
- ClassNotFoundException 找不到類異常
- IllegalArgumentException(非法參數異常)

常見的 Checked Exception 異常:

- IOException (操作輸入流和輸出流時可能出現的異常)
- ClassCastException(類型轉換異常類)

有興趣可以看我之前寫得這篇文章:
Java程序員必備:異常的十個關鍵知識點

CAS機制是什麼,如何解決ABA問題?

CAS涉及三個操作數

  • 1.需要讀寫的內存地址V
  • 2.進行比較的預期原值A
  • 3.擬寫入的新值B
    如果內存位置的值V與預期原A值相匹配,那麼處理器會自動將該位置值更新爲新值B。

CAS思想:要進行更新時,認爲位置V上的值還是跟A值相等,如果是是相等,就認爲它沒有被別的線程更改過,即可更新爲B值。否則,認爲它已經被別的線程修改過,不更新爲B的值,返回當前位置V最新的值。

有興趣的朋友可以看一下我這篇文章,一次CAS思想解決實際問題:
CAS樂觀鎖解決併發問題的一次實踐

深拷貝和淺拷貝區別

淺拷貝

複製了對象的引用地址,兩個對象指向同一個內存地址,所以修改其中任意的值,另一個值都會隨之變化。

深拷貝

將對象及值複製過來,兩個對象修改其中任意的值另一個值不會改變

談談序列化與反序列化

  • 序列化是指將對象轉換爲字節序列的過程,而反序列化則是將字節序列轉換爲對象的過程。
  • Java對象序列化是將實現了Serializable接口的對象轉換成一個字節序列,能夠通過網絡傳輸、文件存儲等方式傳輸 ,傳輸過程中卻不必擔心數據在不同機器、不同環境下發生改變,也不必關心字節的順序或其他任何細節,並能夠在以後將這個字節序列完全恢復爲原來的對象。

這篇文章寫得很好:
Java Serializable:明明就一個空的接口嘛

==與equlas有什麼區別?

==

  • 如果是基本類型,==表示判斷它們值是否相等;
  • 如果是引用對象,==表示判斷兩個對象指向的內存地址是否相同。

equals

  • 如果是字符串,表示判斷字符串內容是否相同;
  • 如果是object對象的方法,比較的也是引用的內存地址值;
  • 如果自己的類重寫equals方法,可以自定義兩個對象是否相等。

談談AQS 原理以及AQS同步組件

AQS原理面試題的核心回答要點

  • state 狀態的維護。
  • CLH隊列
  • ConditionObject通知
  • 模板方法設計模式
  • 獨佔與共享模式。
  • 自定義同步器。
  • AQS全家桶的一些延伸,如:ReentrantLock等。

可以看我這篇文章:AQS解析與實戰

final、finalize()、finally的區別

  • final是關鍵字,用於修飾類、成員變量和成員方法。
  • Finalize是object類中的一個方法,子類可以重寫finalize()方法實現對資源的回收。
  • finally一般跟try一起,出現在異常處理代碼塊中

synchronized 底層如何實現?

可以看一下我這篇文章:
Synchronized解析——如果你願意一層一層剝開我的心

Java線程池的原理?線程池有哪些?線程池工廠有哪些線程池類型,及其線程池參數是什麼?

對於Java線程池,這個流程圖比較重要:

可以看我這篇文章:
面試必備:Java線程池解析

待更新

還有哪些經典Java面試題呢?你也可以告訴我,哈哈。

參考與感謝

個人公衆號

  • 如果你是個愛學習的好孩子,可以關注我公衆號,一起學習討論。
  • 如果你覺得本文有哪些不正確的地方,可以評論,也可以關注我公衆號,私聊我,大家一起學習進步哈。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章