1. 程式人生 > >面試題及答案總結(實時更新)

面試題及答案總結(實時更新)

一、Mybatis中DAO介面的實現機制
答:首先Mybatis會載入配置檔案,根據配置檔案生成sqlsessionFactory工廠,通過工廠獲取sqlsession動態代理物件,將DAO介面交給sqlsession這個動態代理物件,它就會通過invoke方法進行載入mapper、拼接sql、建立preparestream、執行sql等一系列操作實現對資料庫的操作。
二、prototype的應用場景及基本用法。
簡單的來說,無論何時,我們建立的每一個函式都有一個prototype屬性,這個屬性是一個指標,指向一個物件,這個物件包含了通過呼叫該建構函式所建立的物件共享的屬性和方法。其實我們平常的叫法就是指:prototype就是通過該建構函式建立的某個例項的原型物件,但是其實prototype是每個建構函式的屬性而已,只能說萬物皆物件罷了。
   原型物件的優點是:所有的物件例項都可以共享它包含的屬性和方法。這一點可以在建構函式裡就可以看出來,因為建構函式在函式裡面就定義了物件的例項資訊,而原型物件可以在任何地方定義屬性和方法。例如:

function Person(){}
Person.prototype.name = 'bangbang';
Person.prototype.age = 18;
Person.prototype.job = 'programmer';
Person.prototype.dream = function(){
    console.log('Change yourself');
}

var person1 = new Person();
person1.dream();    //Change yourself

var person2 = new Person();
person2.dream();
//判斷兩個例項繼承的方法和屬性是否全等
console.log(person1.dream === person2.dream);
console.log(person1.age === person2.age);

三、promise的應用場景及基本用法
promise是承諾的意思,它會根據載入方法的執行結果執行不同的方法。
我們先看一個最簡單的Promise例子:生成一個0-2之間的隨機數,如果小於1,則等待一段時間後返回成功,否則返回失敗:

function test(resolve, reject) {
    var timeOut = Math.random() * 2;
    log('set timeout to: ' + timeOut + ' seconds.');
    setTimeout(function () {
        if (timeOut < 1) {
            log('call resolve()...');
            resolve('200 OK');
        }
        else {
            log('call reject()...');
            reject('timeout in ' + timeOut + ' seconds.');
        }
    }, timeOut * 1000);
}

這個test()函式有兩個引數,這兩個引數都是函式,如果執行成功,我們將呼叫resolve(‘200 OK’),如果執行失敗,我們將呼叫reject(‘timeout in ’ + timeOut + ’ seconds.’)。可以看出,test()函式只關心自身的邏輯,並不關心具體的resolve和reject將如何處理結果。

有了執行函式,我們就可以用一個Promise物件來執行它,並在將來某個時刻獲得成功或失敗的結果:

var p1 = new Promise(test);
var p2 = p1.then(function (result) {
    console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) {
    console.log('失敗:' + reason);
});

變數p1是一個Promise物件,它負責執行test函式。由於test函式在內部是非同步執行的,當test函式執行成功時,我們告訴Promise物件:

// 如果成功,執行這個函式:
p1.then(function (result) {
    console.log('成功:' + result);
});
當test函式執行失敗時,我們告訴Promise物件:

p2.catch(function (reason) {
    console.log('失敗:' + reason);
});

Promise物件可以串聯起來,所以上述程式碼可以簡化為:

new Promise(test).then(function (result) {
    console.log('成功:' + result);
}).catch(function (reason) {
    console.log('失敗:' + reason);
});

四、String.intern()方法。
答:String.intern()方法主要用於將建立的字串物件放入常量池,jdk1.7之間是直接複製物件到常量池、1.7之後是複製引用地址到常量池,他將通過new建立的大量相同字串變成一份從而節省了儲存空間。
五、JDK的類載入機制、ClassLoader及雙親委派模型。
答:JDK的類載入機制:
1.載入
(1)通過一個類的全限定名來獲取其定義的二進位制位元組流。
(2)將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構。
(3)在Java堆中生成一個代表這個類的java.lang.Class物件,作為對方法區中這些資料的訪問入口。
2.驗證
(1)檔案格式的驗證:驗證位元組流是否符合Class檔案格式的規範,並且能被當前版本的虛擬機器處理,該驗證的主要目的是保證輸入的位元組流能正確地解析並存儲於方法區之內。經過該階段的驗證後,位元組流才會進入記憶體的方法區中進行儲存,後面的三個驗證都是基於方法區的儲存結構進行的。
(2)元資料驗證:對類的元資料資訊進行語義校驗(其實就是對類中的各資料型別進行語法校驗),保證不存在不符合Java語法規範的元資料資訊。
(3)位元組碼驗證:該階段驗證的主要工作是進行資料流和控制流分析,對類的方法體進行校驗分析,以保證被校驗的類的方法在執行時不會做出危害虛擬機器安全的行為。
(4)符號引用驗證:這是最後一個階段的驗證,它發生在虛擬機器將符號引用轉化為直接引用的時候(解析階段中發生該轉化,後面會有講解),主要是對類自身以外的資訊(常量池中的各種符號引用)進行匹配性的校驗。
3.準備
(1)這時候進行記憶體分配的僅包括類變數(static),而不包括例項變數,例項變數會在物件例項化時隨著物件一塊分配在Java堆中。
(2)這裡所設定的初始值通常情況下是資料型別預設的零值(如0、0L、null、false等),而不是被在Java程式碼中被顯式地賦予的值。
假設一個類變數的定義為:
public static int value = 3;
那麼變數value在準備階段過後的初始值為0,而不是3,因為這時候尚未開始執行任何Java方法,而把value賦值為3的putstatic指令是在程式編譯後,存放於類構造器()方法之中的,所以把value賦值為3的動作將在初始化階段才會執行。
4.解析
解析動作主要針對類或介面、欄位、類方法、介面方法四類符號引用進行,分別對應於常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info四種常量型別。
5.初始化
6.使用
7.解除安裝
ClassLoader:
1.啟動類載入器:Bootstrap ClassLoader
2.擴充套件類載入器:Extension ClassLoader
3.應用程式類載入器:Application ClassLoader
4.自定義類載入器
雙親委派模型:
雙親委派模型的工作流程是:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把請求委託給父載入器去完成,依次向上,因此,所有的類載入請求最終都應該被傳遞到頂層的啟動類載入器中,只有當父載入器在它的搜尋範圍中沒有找到所需的類時,即無法完成該載入,子載入器才會嘗試自己去載入該類。
使用雙親委派模型來組織類載入器之間的關係,有一個很明顯的好處,就是Java類隨著它的類載入器(說白了,就是它所在的目錄)一起具備了一種帶有優先順序的層次關係,這對於保證Java程式的穩定運作很重要。例如,類java.lang.Object類存放在JDK\jre\lib下的rt.jar之中,因此無論是哪個類載入器要載入此類,最終都會委派給啟動類載入器進行載入,這邊保證了Object類在程式中的各種類載入器中都是同一個類。
六、Executor、ExecutorService、Executors、ThreadPoolExecutor之間的關係。
答:Executor:是一個執行緒池框架,它實現了方法和執行緒的分離,並且避免了頻繁開啟關閉執行緒池產生的資源浪費。
ExecutorService :ExecutorService 介面 對 Executor 介面進行了擴充套件,提供了返回 Future 物件,終止,關閉執行緒池等方法。當呼叫 shutDown 方法時,執行緒池會停止接受新的任務,但會完成正在 pending 中的任務。
Executors:Executors 是一個工具類,類似於 Collections。提供工廠方法來建立不同型別的執行緒池,比如 FixedThreadPool 或 CachedThreadPool。
ThreadPoolExecutor:是執行緒池的真正實現,他通過構造方法的一系列引數,來構成不同配置的執行緒池。
在這裡插入圖片描述
七、CountDownLatch、CyclicBarrier、Semaphore適用場景
CountDownLatch:位於java.util.concurrent包下,利用它可以實現類似計數器的功能。比如有一個任務A,它要等待其他4個任務執行完畢之後才能執行,此時就可以利用CountDownLatch來實現這種功能了。
CyclicBarrier:字面意思迴環柵欄,通過它可以實現讓一組執行緒等待至某個狀態之後再全部同時執行。叫做迴環是因為當所有等待執行緒都被釋放以後,CyclicBarrier可以被重用。我們暫且把這個狀態就叫做barrier,當呼叫await()方法之後,執行緒就處於barrier了。
Semaphore:翻譯成字面意思為 訊號量,Semaphore可以控同時訪問的執行緒個數,通過 acquire() 獲取一個許可,如果沒有就等待,而 release() 釋放一個許可。
八、ConcurrentHashMap在JDK1.7和JDK1.8實現的不同之處。
在JDK1.7版本中,ConcurrentHashMap的資料結構是由一個Segment陣列和多個HashEntry組成,Segment陣列的意義就是將一個大的table分割成多個小的table來進行加鎖,也就是鎖分離技術,而每一個Segment元素儲存的是HashEntry陣列+連結串列,這個和HashMap的資料儲存結構一樣
JDK1.8的實現已經摒棄了Segment的概念,而是直接用Node陣列+連結串列+紅黑樹的資料結構來實現,併發控制使用Synchronized和CAS來操作,整個看起來就像是優化過且執行緒安全的HashMap,雖然在JDK1.8中還能看到Segment的資料結構,但是已經簡化了屬性,只是為了相容舊版本
九、請簡述ObServer模式及其在JDK中的應用。
定義物件間的一種一對多依賴關係,使得每當一個物件狀態發生改變時,其相關依賴物件皆得到通知並被自動更新。觀察者模式又叫做釋出-訂閱(Publish/Subscribe)模式、模型-檢視(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。
JDK應用觀察者模式的類:
•java.util.Observer/java.util.Observable

•java.util.EventListener (所有子類)

•javax.servlet.http.HttpSessionBindingListener

•javax.servlet.http.HttpSessionAttributeListener

•javax.faces.event.PhaseListener
十、深拷貝和淺拷貝的區別
淺拷貝:
被複制物件的所有變數都含有與原來的物件相同的值,而所有的對其他物件的引用仍然指向原來的物件。即物件的淺拷貝會對“主”物件進行拷貝,但不會複製主物件裡面的物件。”裡面的物件“會在原來的物件和它的副本之間共享。
簡而言之,淺拷貝僅僅複製所考慮的物件,而不復制它所引用的物件
深拷貝:
深拷貝是一個整個獨立的物件拷貝,深拷貝會拷貝所有的屬性,並拷貝屬性指向的動態分配的記憶體。當物件和它所引用的物件一起拷貝時即發生深拷貝。深拷貝相比於淺拷貝速度較慢並且花銷較大。
簡而言之,深拷貝把要複製的物件所引用的物件都複製了一遍。