佛山英文網(wǎng)建站國(guó)外引流推廣軟件
前言
之前為了準(zhǔn)備面試,收集整理了一些面試題。
本篇文章更新時(shí)間2023年12月27日。
最新的內(nèi)容可以看我的原文:https://www.yuque.com/wfzx/ninzck/cbf0cxkrr6s1kniv
設(shè)計(jì)模式
單例共有幾種寫法?
細(xì)分起來就有9種:懶漢(初始加載資源過多時(shí)使用)、餓漢、靜態(tài)內(nèi)部類、枚舉(防止反序列化創(chuàng)建新對(duì)象)、ThreadLocal單例
建造者模式和工廠方法模式的區(qū)別是什么?
工廠模式注重創(chuàng)建一個(gè)產(chǎn)品,不關(guān)心創(chuàng)建細(xì)節(jié);
建造者模式也是創(chuàng)建一個(gè)產(chǎn)品,同時(shí)關(guān)注組成的細(xì)節(jié)。
總結(jié)就是一個(gè)關(guān)注整體、一個(gè)關(guān)注細(xì)節(jié)。
簡(jiǎn)單工廠、工廠方法和抽象工廠之間的區(qū)別是什么?
使用場(chǎng)景有一些區(qū)別:
- 簡(jiǎn)單工廠:適用于類關(guān)系簡(jiǎn)單、不復(fù)雜 并且 確定之后幾乎不擴(kuò)展的情況。
- 工廠方法:適用于需要根據(jù)運(yùn)行時(shí)上下文信息動(dòng)態(tài)選擇實(shí)現(xiàn)類的時(shí)候。
- 抽象工廠方法模式:適用于創(chuàng)建一組相關(guān) 或 相互依賴的對(duì)象時(shí)使用。
橋接模式與適配器模式的區(qū)別是什么?
從目的來看,橋接模式關(guān)注的是分離,適配器關(guān)注的是合并、配合。
適配器模式分為哪幾類?每類的應(yīng)用場(chǎng)景是什么?
類適配器、對(duì)象適配器、接口適配器。
裝飾器模式與代理模式的區(qū)別是什么?
共同點(diǎn):都是讓兩個(gè)類一起配合工作。
區(qū)別:目標(biāo)、側(cè)重不一樣
- 裝飾器模式:側(cè)重?cái)U(kuò)展功能、增加職能;
- 代理模式:側(cè)重對(duì)被代理對(duì)象的訪問控制。
模板設(shè)計(jì)模式的適用場(chǎng)景是什么?
適用于流程固定但是具體實(shí)現(xiàn)有區(qū)別的場(chǎng)景。
策略模式的優(yōu)缺點(diǎn)是什么?
優(yōu)點(diǎn):避免多重條件轉(zhuǎn)移語(yǔ)句、算法實(shí)現(xiàn)跟使用者分離、避免重復(fù)代理。
缺點(diǎn):必須要知道所有策略類
擴(kuò)展:可結(jié)合工廠模式、享元模式。
責(zé)任鏈模式的優(yōu)缺點(diǎn)有哪些?
優(yōu)點(diǎn):減少對(duì)象之間的耦合,靈活指派處理者。
缺點(diǎn):較長(zhǎng)的責(zé)任鏈,可能會(huì)影響性能。
訪問者模式的適用場(chǎng)景及優(yōu)缺點(diǎn)有哪些?
適用場(chǎng)景:數(shù)據(jù)結(jié)構(gòu) 與 數(shù)據(jù)操作 分離 ;
優(yōu)點(diǎn):容易擴(kuò)展新操作;
缺點(diǎn):增加新接收比較麻煩。
泛型的上限限定和下限限定是什么?
上限限定:是某個(gè)類或其子類;
public static <T extends MyClass> void addToList(List<T> list) {}
下限限定:是某個(gè)類或其超類。
public static void addToList(List<? super AbstractMyClass> list) {}
泛型的類型擦除和僑界方法是什么?
類型擦除:編譯期間將泛型替換為普通類型,沒有指定上、下限定的話就是Object,否則就是指定的邊界類。
Java基礎(chǔ)
反射的適用場(chǎng)景有哪些?
應(yīng)用場(chǎng)景:動(dòng)態(tài)加載類和實(shí)例化對(duì)象、動(dòng)態(tài)代理和AOP、獲取注解信息、編寫通用的框架和工具類。
反射的優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):動(dòng)態(tài)靈活;
缺點(diǎn):1.性能開銷:反射涉及動(dòng)態(tài)解析類型和方法的調(diào)用,有一定的性能開銷;2.安全性問題:繞過訪問控制修飾符的限制;3.可讀性變差。
靜態(tài)嵌套類與內(nèi)部類區(qū)別
靜態(tài)嵌套類不依賴外部類就可以進(jìn)行實(shí)例化;
內(nèi)部類要進(jìn)行實(shí)例化的話,需要先實(shí)例化外部類。
抽象方法不能被static、native、synchronized修飾?
對(duì)
當(dāng)一個(gè)線程進(jìn)入一個(gè)對(duì)象的 synchronized 方法 A 之后, 其它線程是否可進(jìn)入此對(duì)象的 synchronized 方法 B?
不能。只能進(jìn)入非synchronized的方法。
對(duì)于同一個(gè)對(duì)象或者class來說。
finally 中的代碼一定會(huì)執(zhí)行嗎?
不一定,比如虛擬機(jī)終止時(shí)、try內(nèi)死循環(huán)。
SPI 是什么
提供一套標(biāo)準(zhǔn)接口,讓服務(wù)提供方(第三方)實(shí)現(xiàn),然后使用合適的方式(比如 Service Loader)發(fā)現(xiàn)這個(gè)服務(wù)實(shí)現(xiàn)并調(diào)用。
SPI 和 API 有啥區(qū)別?
目的不一樣:SPI 用于擴(kuò)展,讓第三方實(shí)現(xiàn);API 是對(duì)外提供服務(wù),不支持?jǐn)U展。
序列化和反序列化是什么?
序列化:將對(duì)象持久化到磁盤、寫到數(shù)據(jù)庫(kù)或在網(wǎng)絡(luò)傳輸時(shí),需要將對(duì)象序列化成二進(jìn)制流;
反序列化:將二進(jìn)制流數(shù)據(jù)還原成對(duì)象。
BIO、NIO 和 AIO 的區(qū)別?
BIO:同步阻塞IO,知道數(shù)據(jù)準(zhǔn)備就緒、拷貝完成才繼續(xù)執(zhí)行;
NIO:同步非阻塞IO,Java中采用IO多路復(fù)用模型,避免了CPU資源的浪費(fèi)。它可以將多個(gè)通道(Channel)注冊(cè)到選擇器(Selector),選擇器通過select系統(tǒng)調(diào)用進(jìn)行監(jiān)控,當(dāng)數(shù)據(jù)準(zhǔn)備好之后讓用戶線程繼續(xù)執(zhí)行。
在linux2.6內(nèi)核,還支持epoll系統(tǒng)調(diào)用,能監(jiān)控?zé)o限多的FD,而且不會(huì)隨FD增多而降低效率。
AIO:異步IO,基于事件以及回調(diào)機(jī)制實(shí)現(xiàn),當(dāng)數(shù)據(jù)準(zhǔn)備好的時(shí)候,系統(tǒng)通知用戶線程進(jìn)行后續(xù)操作。
什么是語(yǔ)法糖?
為了方便程序而設(shè)計(jì)的特殊語(yǔ)法。對(duì)編程功能沒有影響,主要是讓代碼更加簡(jiǎn)潔。
什么是檢查異常,不受檢查異常,運(yùn)行時(shí)異常?并分別舉例說明
檢查異常(Checked Exceptions):這類異常是Exception及其子類的成員,它們?cè)诰幾g時(shí)被強(qiáng)制要求處理,否則程序無(wú)法通過編譯。例如,如果程序要訪問一個(gè)文件,但該文件不存在,就會(huì)出現(xiàn)一個(gè)IOException,這是一個(gè)檢查異常。
運(yùn)行時(shí)異常(Runtime Exceptions):這類異常是RuntimeException類及其子類的成員,它們?cè)诔绦蜻\(yùn)行時(shí)可能發(fā)生,但不強(qiáng)制要求處理。例如,如果程序中出現(xiàn)空指針異常(NullPointerException)或數(shù)組越界異常(IndexOutOfBoundsException),這些都是運(yùn)行時(shí)異常。運(yùn)行時(shí)異常通常是由程序的邏輯錯(cuò)誤引起的,因此程序應(yīng)該盡量避免這些錯(cuò)誤。
不受檢查異常:運(yùn)行時(shí)發(fā)生,一般是程序邏輯問題引起的。
finally塊一定會(huì)執(zhí)行嗎?
JVM停止時(shí)不執(zhí)行;for循環(huán)內(nèi)的break、return不會(huì)。
public static void main(String[] args) {for (int i = 0; i < 2; i++) {try {System.out.println("開始try塊");if(i == 0) {
// break;return;}} catch (Exception e) {System.out.println("執(zhí)行了catch塊,異常信息為:" + e.getMessage());} finally {System.out.println("執(zhí)行finally塊");}}
}
try、catch、finally語(yǔ)句塊的執(zhí)行順序
如上。
一個(gè)空Object對(duì)象的占多大空間?
64位操作系統(tǒng)下,等于對(duì)象頭的大小,即16字節(jié)。關(guān)閉指針壓縮則是32
對(duì)象的組成
對(duì)象頭+實(shí)際數(shù)據(jù)+對(duì)其填充。
對(duì)象頭組成
- Mark Word(標(biāo)記字段):Mark Word是對(duì)象頭的核心部分,它用于存儲(chǔ)對(duì)象的標(biāo)記信息和運(yùn)行時(shí)狀態(tài)。Mark Word的具體結(jié)構(gòu)和含義可能會(huì)因不同的虛擬機(jī)實(shí)現(xiàn)而有所差異,但通常包括以下內(nèi)容:
- 對(duì)象的哈希碼(HashCode):用于支持對(duì)象的哈希操作,如在HashMap中使用。
- 鎖狀態(tài)標(biāo)志(Lock State Flags):用于支持對(duì)象的同步操作,如加鎖、解鎖等。
- GC標(biāo)記(GC Mark):用于垃圾回收器標(biāo)記對(duì)象的存活狀態(tài)。
- 偏向鎖(Biased Lock):用于支持對(duì)象的偏向鎖優(yōu)化,以提高單線程訪問對(duì)象的性能。
- 類型指針(Class Metadata Pointer):類型指針指向?qū)ο笏鶎俚念愒獢?shù)據(jù)(Class Metadata),包括對(duì)象的類型信息、方法表(Virtual Method Table)等。通過類型指針,虛擬機(jī)可以確定對(duì)象的類型和方法的調(diào)用。
拆箱和裝箱的過程
Integer數(shù)值范圍在[-128, 127]的,將從對(duì)象池中獲取。
Integer a = 1
:自動(dòng)裝箱,從對(duì)象池中獲取對(duì)象。
a++
;先拆箱,自增之后,裝箱
多態(tài)實(shí)現(xiàn)原理
回顧多態(tài)的三個(gè)必要條件:
區(qū)分實(shí)現(xiàn)方式與實(shí)現(xiàn)原理,實(shí)現(xiàn)方式:繼承+重寫。
而多態(tài)的實(shí)現(xiàn)原理是:動(dòng)態(tài)綁定 + 虛擬方法表
。
而非私有、非靜態(tài)和非 final 的方法是動(dòng)態(tài)綁定的;
同一個(gè)class的多個(gè)實(shí)例,共用同一張?zhí)摂M方法表。
虛擬方法表在鏈接階段&初始化。
集合
ArrayList的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):高效的隨機(jī)訪問;尾插、尾刪較快;1.5倍動(dòng)態(tài)擴(kuò)容;
缺點(diǎn):動(dòng)態(tài)擴(kuò)容需要重新分配內(nèi)存,影響性能;不是線程安全;刪除、插入元素可能需要移動(dòng)元素,會(huì)損失一些性能;可能會(huì)有一些空間的浪費(fèi)。
ArrayList和ListList區(qū)別
ArrayList:基于數(shù)組實(shí)現(xiàn),具有高效的隨機(jī)訪問能力;在插入、刪除元素時(shí),可能需要最差的情況下需要
ArrayList初始容量
使用無(wú)參構(gòu)造器創(chuàng)建的ArrayList,默認(rèn)是一個(gè)空數(shù)組,沒有分配空間,直到調(diào)用add()方法,會(huì)將初始容量設(shè)置為10。
ArrayList擴(kuò)容機(jī)制
每次擴(kuò)容原來的一半。
以add(T t)
為入口,插入元素前先檢查容量,如果不夠,那就計(jì)算要擴(kuò)容到多少大小,然后檢查要擴(kuò)容的大小是否超過限制,最后使用Arrays.copyOf
方法進(jìn)行數(shù)組拷貝。
基層調(diào)用System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 每次擴(kuò)容原來的一半。if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);}
LinkedList是雙向鏈表還是單向鏈表
雙向鏈表。這樣刪除、插入更高效;但是對(duì)比單向鏈表,需要的空間就多一些、實(shí)現(xiàn)也更復(fù)雜一些。
HashMap jdk1.7和1.8的實(shí)現(xiàn)有什么區(qū)別
- 數(shù)據(jù)結(jié)構(gòu):JDK8引入了紅黑樹解決鏈表過長(zhǎng)問題,數(shù)組類型改為Node。
- 哈希沖突解決方式上:JDK7是采用頭插法將元素插入鏈表,JDK8采用尾查法;
- 擴(kuò)容機(jī)制:JDK7擴(kuò)容是在put操作中進(jìn)行,當(dāng)鏈表大于8時(shí)將重建數(shù)組和鏈表,并重新散列;JDK8的話,擴(kuò)容是在resize方法進(jìn)行,當(dāng)元素?cái)?shù)量達(dá)到需要調(diào)整的閾值時(shí)(容量*0.75),將觸發(fā)擴(kuò)容方法,創(chuàng)建更大的數(shù)組,將舊元素重新hash分配到新數(shù)組。
HashMap 的長(zhǎng)度為什么是 2 的冪次方
為了讓HashMap存取高效,讓數(shù)據(jù)分配得更均勻,減少哈希碰撞。
HashMap 多線程操作導(dǎo)致死循環(huán)問題
JDK7之前,當(dāng)桶中有多個(gè)元素需要進(jìn)行擴(kuò)容的時(shí)候,多線程同時(shí)進(jìn)行,可能會(huì)生產(chǎn)環(huán)形鏈表,導(dǎo)致死循環(huán)。
JDK8采用了尾插法避免環(huán)形鏈表。
比較 HashSet、LinkedHashSet 和 TreeSet 三者的異同
- 相同:全部實(shí)現(xiàn)Set接口,都不是線程安全的;
- 區(qū)別:最大的區(qū)別是底層結(jié)構(gòu)不同,
- HashSet:hash表;
- LinkedHashSet:鏈表 + hash表、
- TreeSet:紅黑樹。
HashMap底層結(jié)構(gòu)
異同:
JDK8之后,new HashMap()不會(huì)創(chuàng)建一個(gè)長(zhǎng)度為16的數(shù)組了。調(diào)用put的時(shí)候才創(chuàng)建。
JDK8之后,使用Node[]作為數(shù)組類型;
JDK8之后,使用數(shù)組+鏈表+紅黑樹,之前使用的是數(shù)組+鏈表。
下面是數(shù)據(jù)結(jié)構(gòu)圖:
JDK7
JDK8
HashMap什么時(shí)候轉(zhuǎn)紅黑樹?
鏈表長(zhǎng)度大于等于8并且數(shù)組長(zhǎng)度大于等于64時(shí)。
HashMap紅黑樹為什么6的時(shí)候退回鏈表?
鏈表更簡(jiǎn)單、數(shù)據(jù)量小的時(shí)候,鏈表可能查得更快
ConcurrentHashMap底層結(jié)構(gòu)
JDK7
JDK8
跟HashMap類似,采用CAS + synchronized
保證并發(fā)安全。
JDK 1.7 和 JDK 1.8 的 ConcurrentHashMap 實(shí)現(xiàn)有什么不同?
數(shù)據(jù)結(jié)構(gòu)不同:JDK7采用分段數(shù)組 + 條目數(shù)組 + 鏈表;JDK8采用數(shù)組 + 鏈表 + 紅黑樹;
并發(fā)程度不同:JDK7并發(fā)程度由分段數(shù)量決定;JDK8對(duì)Node加鎖,并發(fā)讀更大。
ConcurrentHashMap 為什么 key 和 value 不能為 null?
從設(shè)計(jì)上來說:用null的話無(wú)法區(qū)分是找不到才返回空還是原本就是null;
從源碼上來看:需要取hashCode,空的key會(huì)拋出空指針異常。
ConcurrentHashMap 能保證復(fù)合操作的原子性嗎?
不能。但是提供了一些復(fù)合操作原子性的方法,如putIfXX、compute等。