來自面試官的技術(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ù)整理中...