來自面試官的技術(shù)面試題
作者:xcbeyond
瘋狂源自夢(mèng)想,技術(shù)成就輝煌!微信公眾號(hào):《程序猿技術(shù)大咖》號(hào)主,專注后端開發(fā)多年,擁有豐富的研發(fā)經(jīng)驗(yàn),樂于技術(shù)輸出、分享,現(xiàn)階段從事微服務(wù)架構(gòu)項(xiàng)目的研發(fā)工作,涉及架構(gòu)設(shè)計(jì)、技術(shù)選型、業(yè)務(wù)研發(fā)等工作。對(duì)于Java、微服務(wù)、數(shù)據(jù)庫、Docker有深入了解,并有大量的調(diào)優(yōu)經(jīng)驗(yàn)。
最近為公司面試了不少Java開發(fā),有工作一兩年的,也有工作十來年的人,在面試他人前,自己也需準(zhǔn)備一下,免得錯(cuò)失人才,或者誤導(dǎo)他人。為了更好的面試他人,所以我也會(huì)準(zhǔn)備一番,全當(dāng)查漏補(bǔ)缺(畢竟好東西我也不是很清楚的),因此,就最近面試情況及問題,進(jìn)行匯總整理如下。
我一般面試提問,會(huì)從下面三個(gè)方面發(fā)問:
自我介紹
技術(shù)、框架
Java基礎(chǔ)
自我介紹
自我介紹,老生常談的話題,大部分面試官都以此作為面試的開口,以了解面試者的基本信息(工作時(shí)間、工作經(jīng)歷)、做過哪些/哪類項(xiàng)目、會(huì)什么技術(shù)、擅長什么等。
溫馨提示,個(gè)人自我介紹最好提前有所準(zhǔn)備下,不至于說話磕磕碰碰,更重要的是要實(shí)事求是。自我介紹,也是對(duì)面試官產(chǎn)生第一印象開端,有了好的印象,才會(huì)有接下來的事情,對(duì)你后續(xù)面試及面試結(jié)果有一定的幫助。
技術(shù)、框架
技術(shù)及框架,在此進(jìn)行分類整理(沒有先后順序),如下:
線程部分
1、使用過線程么?線程如何實(shí)現(xiàn)?
通過繼承 Thread 類、實(shí)現(xiàn)Runnable 接口,在run方法中實(shí)現(xiàn)功能或業(yè)務(wù)邏輯。
2、線程中start和run方法有什么區(qū)別和聯(lián)系?
調(diào)用start方法可啟動(dòng)線程,這時(shí)此線程處于就緒(可運(yùn)行)狀態(tài),并沒有運(yùn)行,一旦得到cpu時(shí)間片,就開始執(zhí)行run()方法
,即:線程要執(zhí)行的內(nèi)容。
而run方法只是線程里面一個(gè)普通方法的調(diào)用而已,還是在主線程里執(zhí)行。如果直接調(diào)用run方法,程序中依然只有主線程這一個(gè)線程,其程序執(zhí)行路徑還是只有一條,還是要順序執(zhí)行,還是要等待run方法體執(zhí)行完畢后才可繼續(xù)執(zhí)行下面的代碼,這樣就沒有達(dá)到寫線程的目的。
public static void main(String args[]) {
Thread t = new Thread() {
public void run() {
pong();
}
};
t.start();
System.out.print("ping");
}
public static void pong() {
System.out.print("pong");
}
輸出結(jié)果: pingpong
public static void main(String args[]) {
Thread t = new Thread() {
public void run() {
pong();
}
};
t.run();
System.out.print("ping");
}
public static void pong() {
System.out.print("pong");
}
輸出結(jié)果:pongping
通過以上兩個(gè)程序?qū)嵗梢院苋菀椎膮^(qū)分出start()方法和run()方法的區(qū)別:
t.start(); 該行代碼相當(dāng)于是啟動(dòng)線程,
t.run(); 該行代碼相當(dāng)于是使用t這個(gè)類中的run方法而已。
3、了解過線程死鎖么?如何有效的避免線程死鎖?
死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外界作用下,它們都將無法進(jìn)行下去。此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。
避免死鎖最簡單的方法就是阻止循環(huán)等待條件,將系統(tǒng)中所有的資源設(shè)置標(biāo)志位、排序,規(guī)定所有的進(jìn)程申請(qǐng)資源必須以一定的順序(升序或降序)做操作來避免死鎖。
4、項(xiàng)目中有沒有用過線程池 ?怎么用的 ?
使用過。我們使用線程的時(shí)候就去創(chuàng)建一個(gè)線程,這樣實(shí)現(xiàn)起來非常簡便,但是就會(huì)有一個(gè)問題:如果并發(fā)的線程數(shù)量很多,并且每個(gè)線程都是執(zhí)行一個(gè)時(shí)間很短的任務(wù)就結(jié)束了,這樣頻繁創(chuàng)建線程就會(huì)大大降低系統(tǒng)的效率,因?yàn)轭l繁創(chuàng)建線程和銷毀線程需要時(shí)間。
通過使用線程池就可以解決這個(gè)問題,使得線程可以復(fù)用,就是執(zhí)行完一個(gè)任務(wù),并不被銷毀,而是可以繼續(xù)執(zhí)行其他的任務(wù)。Java的線程池最核心是ThreadPoolExecutor類,線程池底層都是通過 ThreadPoolExecutor 來實(shí)現(xiàn)的:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
其中參數(shù)的意思分別為:
corePoolSize:線程池里最小線程數(shù)
maximumPoolSize:線程池里最大線程數(shù)量,超過最大線程時(shí)候會(huì)使用 RejectedExecutionHandler
keepAliveTime:線程最大的存活時(shí)間,超過這個(gè)時(shí)間就會(huì)被回收
unit:線程最大的存活時(shí)間的單位
workQueue:緩存需要執(zhí)行的異步任務(wù)的隊(duì)列
threadFactory:新建線程工廠
handler:拒絕策略,表示當(dāng)workQueue已滿,且池中的線程數(shù)達(dá)到maximumPoolSize時(shí),線程池拒絕添加新任務(wù)時(shí)采取的策略。DiscardPolicy:拋棄當(dāng)前任務(wù),DiscardOldestPolicy:扔掉最舊的,CallerRunsPolicy:由向線程池提交任務(wù)的線程來執(zhí)行該任務(wù),AbortPolicy:拋出 RejectedExecutionException 異常。
MyBatis 部分
1、mybatis 中$ 與 # 的區(qū)別?
都是可以來傳遞參數(shù)的,不過 # 可以方防止sql 注入,而 $ 就是字符串拼接的方式處理,可能會(huì)有sql 注入的問題。
#{} 在預(yù)處理時(shí),會(huì)把參數(shù)部分用一個(gè)占位符 ? 代替 ,變成了如下的 sql 語句:
select * from user where name = ?;
而 ${} 則只是簡單的字符串拼接,在動(dòng)態(tài)解析階段就直接拼接成了 最終的sql 語句:
select * from user where name = 'xcbeyond';
2、當(dāng)實(shí)體類中的屬性名和表中的字段名不一樣 ,怎么辦 ?
第1種: 通過在查詢的sql語句中定義字段名的別名,讓字段名的別名和實(shí)體類的屬性名一致。
<select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”>
select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
</select>
第2種: 通過<resultMap>來映射字段名和實(shí)體類屬性名的一一對(duì)應(yīng)的關(guān)系。
<select id="getOrder" parameterType="int" resultMap="orderresultmap">
select * from orders where order_id=#{id}
</select>
<resultMap type=”me.gacl.domain.order” id=”orderresultmap”>
<!–用id屬性來映射主鍵字段–>
<id property=”id” column=”order_id”>
<!–用result屬性來映射非主鍵字段,property為實(shí)體類屬性名,column為數(shù)據(jù)表中的屬性–>
<result property = “orderno” column =”order_no”/>
<result property=”price” column=”order_price” />
</reslutMap>
3、MyBatis中怎么實(shí)現(xiàn)一個(gè)動(dòng)態(tài)SQL?
Mybatis動(dòng)態(tài)sql可以在Xml映射文件內(nèi),以標(biāo)簽的形式編寫動(dòng)態(tài)sql,執(zhí)行原理是根據(jù)表達(dá)式的值 完成邏輯判斷并動(dòng)態(tài)拼接sql的功能。
Mybatis提供了9種動(dòng)態(tài)sql標(biāo)簽:trim | where | set | foreach | if | choose | when | otherwise | bind。
4、Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重復(fù)?
不同的Xml映射文件,如果配置了namespace,那么id可以重復(fù);如果沒有配置namespace,那么id不能重復(fù);
原因就是namespace+id是作為Map<String, MapperStatement>的key使用的,如果沒有namespace,就剩下id,那么,id重復(fù)會(huì)導(dǎo)致數(shù)據(jù)互相覆蓋。有了namespace,自然id就可以重復(fù),namespace不同,namespace+id自然也就不同。
更多詳見Mybatis常見面試題總結(jié)
數(shù)據(jù)庫
1、有沒有使用過視圖?什么場景下會(huì)考慮使用它?
視圖,是一種虛擬的表,具有和一般表相同的功能??梢詫?duì)視圖進(jìn)行增,改,查操作,試圖是由一個(gè)表或者多個(gè)表的行或列的子集,即:是一個(gè)查詢sql的查詢結(jié)果集。
以下場景,一般會(huì)考慮使用視圖:
頻繁使用子查詢。通常會(huì)將頻繁使用的子查詢,創(chuàng)建為一個(gè)視圖,便于共用,以簡化sql量,直接調(diào)用而不是每次都去重復(fù)寫這個(gè)子查詢。
避免直接暴露表結(jié)構(gòu)。需要給其他外部系統(tǒng)、他人提供表數(shù)據(jù)時(shí),可創(chuàng)建一個(gè)對(duì)應(yīng)數(shù)據(jù)的視圖,而不是直接暴露原始表,這樣一定程度上降低風(fēng)險(xiǎn)。
2、有沒有使用過索引?使用索引時(shí)有什么注意事項(xiàng)么?
表添加索引后,一定程度會(huì)加速表的查詢速度,但過多的使用索引將會(huì)造成濫用。雖然索引大大提高了查詢速度,同時(shí)卻會(huì)降低更新表的速度,如對(duì)表進(jìn)行INSERT、UPDATE和DELETE。因?yàn)楦卤頃r(shí),MySQL不僅要保存數(shù)據(jù),還要保存一下索引文件。
使用索引時(shí)的優(yōu)缺點(diǎn)如下:
優(yōu)點(diǎn):
可以通過建立唯一索引或者主鍵索引,保證數(shù)據(jù)庫表中每一行數(shù)據(jù)的唯一性
建立索引可以大大提高檢索的數(shù)據(jù),以及減少表的檢索行數(shù)
在表連接的連接條件 可以加速表與表直接的相連
在分組和排序字句進(jìn)行數(shù)據(jù)檢索,可以減少查詢時(shí)間中 分組 和 排序時(shí)所消耗的時(shí)間(數(shù)據(jù)庫的記錄會(huì)重新排序)
建立索引,在查詢中使用索引 可以提高性能
缺點(diǎn):
在創(chuàng)建索引和維護(hù)索引 會(huì)耗費(fèi)時(shí)間,隨著數(shù)據(jù)量的增加而增加
索引文件會(huì)占用物理空間,除了數(shù)據(jù)表需要占用物理空間之外,每一個(gè)索引還會(huì)占用一定的物理空間
當(dāng)對(duì)表的數(shù)據(jù)進(jìn)行 INSERT,UPDATE,DELETE 的時(shí)候,索引也要?jiǎng)討B(tài)的維護(hù),這樣就會(huì)降低數(shù)據(jù)的維護(hù)速度,(建立索引會(huì)占用磁盤空間的索引文件。一般情況這個(gè)問題不太嚴(yán)重,但如果你在一個(gè)大表上創(chuàng)建了多種組合索引,索引文件的會(huì)膨脹很快)。
3、查詢語句速度很慢,如何優(yōu)化?
可從以下幾個(gè)方面進(jìn)行優(yōu)化:
建索引
減少表之間的關(guān)聯(lián)
優(yōu)化sql,盡量讓sql很快定位數(shù)據(jù),不要讓sql做全表查詢,應(yīng)該走索引,把數(shù)據(jù) 量大的表排在前面
簡化查詢字段,沒用的字段不要,已經(jīng)對(duì)返回結(jié)果的控制,盡量返回少量數(shù)據(jù),避免slect * 查詢
盡量用PreparedStatement來查詢,不要用Statement
下面列舉一些優(yōu)化小技巧:
技巧1 比較運(yùn)算符能用 “=”就不用“<>”
“=”增加了索引的使用幾率。
技巧2 明知只有一條查詢結(jié)果,那請(qǐng)使用 “LIMIT 1”
“LIMIT 1”可以避免全表掃描,找到對(duì)應(yīng)結(jié)果就不會(huì)再繼續(xù)掃描了。
技巧3 為列選擇合適的數(shù)據(jù)類型
能用TINYINT就不用SMALLINT,能用SMALLINT就不用INT,道理你懂的,磁盤和內(nèi)存消耗越小越好嘛。
技巧4 將大的DELETE,UPDATE or INSERT 查詢變成多個(gè)小查詢
能寫一個(gè)幾十行、幾百行的SQL語句是不是顯得逼格很高?然而,為了達(dá)到更好的性能以及更好的數(shù)據(jù)控制,你可以將他們變成多個(gè)小查詢。
技巧5 使用UNION ALL 代替 UNION,如果結(jié)果集允許重復(fù)的話
因?yàn)?UNION ALL 不去重,效率高于 UNION。
技巧6 為獲得相同結(jié)果集的多次執(zhí)行,請(qǐng)保持SQL語句前后一致
這樣做的目的是為了充分利用查詢緩沖。
比如根據(jù)地域和產(chǎn)品id查詢產(chǎn)品價(jià)格,第一次使用了:
那么第二次同樣的查詢,請(qǐng)保持以上語句的一致性,比如不要將where語句里面的id和region位置調(diào)換順序。
技巧7 盡量避免使用 “SELECT *”
如果不查詢表中所有的列,盡量避免使用 SELECT *,因?yàn)樗鼤?huì)進(jìn)行全表掃描,不能有效利用索引,增大了數(shù)據(jù)庫服務(wù)器的負(fù)擔(dān),以及它與應(yīng)用程序客戶端之間的網(wǎng)絡(luò)IO開銷。
技巧8 WHERE 子句里面的列盡量被索引
只是“盡量”哦,并不是說所有的列。因地制宜,根據(jù)實(shí)際情況進(jìn)行調(diào)整,因?yàn)橛袝r(shí)索引太多也會(huì)降低性能。
技巧9 JOIN 子句里面的列盡量被索引
同樣只是“盡量”哦,并不是說所有的列。
技巧10 ORDER BY 的列盡量被索引
ORDER BY的列如果被索引,性能也會(huì)更好。
技巧11 使用 LIMIT 實(shí)現(xiàn)分頁邏輯
不僅提高了性能,同時(shí)減少了不必要的數(shù)據(jù)庫和應(yīng)用間的網(wǎng)絡(luò)傳輸。
技巧12 使用 EXPLAIN 關(guān)鍵字去查看執(zhí)行計(jì)劃
EXPLAIN 可以檢查索引使用情況以及掃描的行。
4、你都使用過Mysql和Oracle數(shù)據(jù)庫,能不能舉例說一下他們都有哪些區(qū)別?
可以從以下幾個(gè)方面說起:
本質(zhì)區(qū)別
MySql是AB 公司開發(fā)的,目前屬于 Oracle 旗下的一個(gè)開源、免費(fèi)的數(shù)據(jù)庫,而Oracle是一個(gè)收費(fèi)的數(shù)據(jù)庫。
SQL語法/句的區(qū)別
(1) 刪除表時(shí),sql關(guān)鍵字的差異。
MySql: drop table if exists tableName
Oracle:drop table tableName
注:Oracle沒有if exists關(guān)鍵字,也沒用類似if exists的SQL語法。
(2)函數(shù)、關(guān)鍵字的差異
MySql中日期的轉(zhuǎn)換用dateformat()函數(shù),Oracle用to_date()與to_char()兩個(gè)函數(shù);
MySql獲取當(dāng)前時(shí)間用NOW(),Oracle用sysdate;
等等
(3)mysql可以實(shí)現(xiàn)自增長主鍵(通過字段的auto_increment屬性);Oracle則需要通過序列(Sequence)來實(shí)現(xiàn)。
(4)索引
在整個(gè)數(shù)據(jù)庫內(nèi),MySql的索引可以同名,也就是說MySql的索引是表級(jí)別的;但是Oracle索引不可以同名,也就是說Oracle的索引是數(shù)據(jù)庫級(jí)別的。
(5)空字符串問題
Oracle中空字符串就是null(也就是說只有null,沒有空字符),而MySql是有null和''區(qū)分的。
對(duì)于使用語句:select * from table1 where user_name <> '' 來查詢列user_name不為空(不為null且不為空字符)時(shí),Oracle會(huì)查不出任何結(jié)果,而MySQL可以正常運(yùn)行。這里MySQL之所以可以得到正確結(jié)果,還因?yàn)楸容^符號(hào)<>會(huì)先將列為null的內(nèi)容進(jìn)行過濾,然后再比較內(nèi)容是否為空字符串。
這就要求一方面,以后在編寫代碼的時(shí)候,盡量保證不會(huì)往數(shù)據(jù)庫插入空字符串''這樣的值,要么保持有數(shù)據(jù),要么保持為null。另外,對(duì)于MySQL中已經(jīng)同時(shí)存在Null和''時(shí),所有判斷是否為null或者''的地方改為判斷列的長度是否為0。
持續(xù)整理中...