java面試題——高級篇

1、集合

Hashmap的原理

源碼分析參考文章:http://www.cnblogs.com/xwdreamer/archive/2012/06/03/2532832.htmlhtml

題目參考文章:http://www.importnew.com/7099.htmljava

總結:mysql

HashMap基於hashing原理,咱們經過put()和get()方法儲存和獲取對象。當咱們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode,讓後找到bucket位置來儲存值對象。當獲取對象時,經過鍵對象的equals()方法找到正確的鍵值對,而後返回值對象。程序員

HashMap使用鏈表來解決碰撞問題,當發生碰撞了,對象將會儲存在鏈表的下一個節點中。 HashMap在每一個鏈表節點中儲存鍵值對對象。web

當兩個不一樣的鍵對象的hashcode相同時會發生什麼? 它們會儲存在同一個bucket位置的鏈表中。鍵對象的equals()方法用來找到鍵值對。面試

面試時可能會問到的問題:redis

1.「你用過HashMap嗎?」 「什麼是HashMap?你爲何用到它?」算法

  用過,HashMap是基於哈希表的Map接口的非同步實現(Hashtable跟HashMap很像,惟一的區別是Hashtalbe中的方法是線程安全的,也就是同步的)。此實現提供全部可選的映射操做,並容許使用null鍵和null值。此類不保證映射的順序,特別是它不保證該順序恆久不變。sql

2.「你知道HashMap的工做原理嗎?」 「你知道HashMap的get()方法的工做原理嗎?數據庫

  答案:HashMap是基於hashing的原理,咱們使用put(key, value)存儲對象到HashMap中,使用get(key)從HashMap中獲取對象。當咱們給put()方法傳遞鍵和值時,咱們先對鍵調用hashCode()方法,返回的hashCode用於找到bucket位置來儲存Entry對象。當獲取對象時,經過鍵對象的equals()方法找到正確的鍵值對,而後返回值對象。

  解釋:這裏關鍵點在於指出,HashMap是在bucket中儲存鍵對象和值對象,做爲Map.Entry。這一點有助於理解獲取對象的邏輯。若是你沒有意識到這一點,或者錯誤的認爲僅僅只在bucket中存儲值的話,你將不會回答如何從HashMap中獲取對象的邏輯。這個答案至關的正確,也顯示出面試者確實知道hashing以及HashMap的工做原理

3.當兩個對象的hashcode相同會發生什麼

  答案:由於hashcode相同,因此它們的bucket位置相同,‘碰撞’會發生。由於HashMap使用鏈表存儲對象,這個Entry(包含有鍵值對的Map.Entry對象)會存儲在鏈表中。

  解釋:這個答案很是的合理,雖然有不少種處理碰撞的方法,這種方法是最簡單的,也正是HashMap的處理方法。

4.若是兩個鍵的hashcode相同,你如何獲取值對象?

  答案:當咱們調用get(k)方法,HashMap會使用鍵對象的hashcode找到bucket位置,找到bucket位置以後,會調用keys.equals()方法去找到鏈表中正確的節點,最終找到要找的值對象。

  解釋:許多狀況下,面試者會在這個環節中出錯,由於他們混淆了hashCode()和equals()方法。由於在此以前hashCode()屢屢出現,而equals()方法僅僅在獲取值對象的時候纔出現。一些優秀的開發者會指出使用不可變的、聲明做final的對象,而且採用合適的equals()和hashCode()方法的話,將會減小碰撞的發生,提升效率。不可變性使得可以緩存不一樣鍵的hashcode,這將提升整個獲取對象的速度,使用String,Interger這樣的wrapper類做爲鍵是很是好的選擇。

5.若是HashMap的大小超過了負載因子(load factor)定義的容量,怎麼辦?

  答案:默認的負載因子大小爲0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)同樣,將會建立原來HashMap大小的兩倍的bucket數組,來從新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫做rehashing,由於它調用hash方法找到新的bucket位置。

  解釋:除非你真正知道HashMap的工做原理,不然你將回答不出這道題。

6.你瞭解從新調整HashMap大小存在什麼問題嗎?

  答案:當從新調整HashMap大小的時候,確實存在條件競爭,由於若是兩個線程都發現HashMap須要從新調整大小了,它們會同時試着調整大小。在調整大小的過程當中,存儲在鏈表中的元素的次序會反過來,由於移動到新的bucket位置的時候,HashMap並不會將元素放在鏈表的尾部,而是放在頭部,這是爲了不尾部遍歷(tail traversing)。若是條件競爭發生了,那麼就死循環了。

  解釋:你可能回答不上來,這時面試官會提醒你當多線程的狀況下,可能產生條件競爭(race condition)。

7.爲何String, Interger這樣的wrapper類適合做爲鍵?

  答案:String, Interger這樣的wrapper類做爲HashMap的鍵是再適合不過了,並且String最爲經常使用。由於String是不可變的,也是final的,並且已經重寫了equals()和hashCode()方法了。其餘的wrapper類也有這個特色。不可變性是必要的,由於爲了要計算hashCode(),就要防止鍵值改變,若是鍵值在放入時和獲取時返回不一樣的hashcode的話,那麼就不能從HashMap中找到你想要的對象。不可變性還有其餘的優勢如線程安全。若是你能夠僅僅經過將某個field聲明成final就能保證hashCode是不變的,那麼請這麼作吧。由於獲取對象的時候要用到equals()和hashCode()方法,那麼鍵對象正確的重寫這兩個方法是很是重要的。若是兩個不相等的對象返回不一樣的hashcode的話,那麼碰撞的概率就會小些,這樣就能提升HashMap的性能。

8.咱們可使用自定義的對象做爲鍵嗎?

  答案:這是前一個問題的延伸。固然你可能使用任何對象做爲鍵,只要它遵照了equals()和hashCode()方法的定義規則,而且當對象插入到Map中以後將不會再改變了。若是這個自定義對象時不可變的,那麼它已經知足了做爲鍵的條件,由於當它建立以後就已經不能改變了。

9.咱們可使用CocurrentHashMap來代替Hashtable嗎?

  答案:這是另一個很熱門的面試題,由於ConcurrentHashMap愈來愈多人用了。咱們知道Hashtable是synchronized的,可是ConcurrentHashMap同步性能更好,由於它僅僅根據同步級別對map的一部分進行上鎖。ConcurrentHashMap固然能夠代替HashTable,可是HashTable提供更強的線程安全性

10.Hashmap爲何大小是2的冪次

  答案爲了hash的平均分佈,減小碰撞值

11.什麼是HashMap的加載因子

  答案:加載因子是表示Hsah表中元素的填滿的程度.若:加載因子越大,填滿的元素越多,好處是,空間利用率高了,但:衝突的機會加大了.反之,加載因子越小,填滿的元素越少,好處是:衝突的機會減少了,但:空間浪費多了.

Arraylist的原理

源碼分析參考文章:http://www.importnew.com/19867.html

問題參考文章:http://www.importnew.com/9928.html

ArrayList是基於數組實現的,是一個動態數組,其容量能自動增加,相似於C語言中的動態申請內存,動態增加內存。

ArrayList不是線程安全的,只能用在單線程環境下,多線程環境下能夠考慮用Collections.synchronizedList(List l)函數返回一個線程安全的ArrayList類,也可使用concurrent併發包下的CopyOnWriteArrayList類。

ArrayList實現了Serializable接口,所以它支持序列化,可以經過序列化傳輸,實現了RandomAccess接口,支持快速隨機訪問,實際上就是經過下標序號進行快速訪問,實現了Cloneable接口,能被克隆。

一、ArrayList的大小是如何自動增長的?你能分享一下你的代碼嗎?

  答案:當有人試圖在arraylist中增長一個對象的時候,Java會去檢查arraylist,以確保已存在的數組中有足夠的容量來存儲這個新的對象。若是沒有足夠容量的話,那麼就會新建一個長度更長的數組,舊的數組就會使用Arrays.copyOf方法被複制到新的數組中去,現有的數組引用指向了新的數組

  源碼:

 1 //ArrayList Add方法:
 2 public boolean add(E e){
 3     ensureCapacity(size+1); //Increment modCount!!
 4     elementData[size++] = e;
 5     return true;
 6 }
 7  
 8 //ensureCapacity方法:處理ArrayList的大小
 9 public void ensureCapacity(int minCapacity) {
10     modCount++;
11     int oldCapacity = elementData.length;
12     if (minCapacity > oldCapacity) {
13     Object oldData[] = elementData;
14     int newCapacity = (oldCapacity * 3)/2 + 1;
15     if (newCapacity < minCapacity)
16         newCapacity = minCapacity;
17     // minCapacity is usually close to size, so this is a win:
18     elementData = Arrays.copyOf(elementData, newCapacity);
19     }
20 }

  解釋:請注意這樣一個狀況:新建了一個數組;舊數組的對象被複制到了新的數組中,而且現有的數組指向新的數組。

二、什麼狀況下你會使用ArrayList?何時你會選擇LinkedList?

  答案:訪問元素比插入或者是刪除元素更加頻繁的時候,你應該使用ArrayList。當你在某個特別的索引中,插入或者是刪除元素更加頻繁,或者你壓根就不須要訪問元素的時候,你會選擇LinkedList。

     這裏的主要緣由是,在ArrayList中訪問元素的最糟糕的時間複雜度是」1″,而在LinkedList中可能就是」n」了。在ArrayList中增長或者刪除某個元素,一般會調用System.arraycopy方法,這是一種極爲消耗資源的操做,所以,在頻繁的插入或者是刪除元素的狀況下,LinkedList的性能會更加好一點。

三、當傳遞ArrayList到某個方法中,或者某個方法返回ArrayList,何時要考慮安全隱患?如何修復安全違規這個問題呢?

  答案:

當array被當作參數傳遞到某個方法中,若是array在沒有被複制的狀況下直接被分配給了成員變量,那麼就可能發生這種狀況,即當原始的數組被調用的方法改變的時候,傳遞到這個方法中的數組也會改變。下面的這段代碼展現的就是安全違規以及如何修復這個問題。

ArrayList被直接賦給成員變量——安全隱患:

修復這個安全隱患:

四、如何複製某個ArrayList到另外一個ArrayList中去?寫出你的代碼?

  答案:下面就是把某個ArrayList複製到另外一個ArrayList中去的幾種技術:

  1. 使用clone()方法,好比ArrayList newArray = oldArray.clone();
  2. 使用ArrayList構造方法,好比:ArrayList myObject = new ArrayList(myTempObject);
  3. 使用Collection的copy方法。

    注意1和2是淺拷貝(shallow copy)。

五、在索引中ArrayList的增長或者刪除某個對象的運行過程?效率很低嗎?解釋一下爲何?

  答案:在ArrayList中增長或者是刪除元素,要調用System.arraycopy這種效率很低的操做,若是遇到了須要頻繁插入或者是刪除的時候,你能夠選擇其餘的Java集合,好比LinkedList。

看一下下面的代碼:

在ArrayList的某個索引i處添加元素:

刪除ArrayList的某個索引i處的元素:

 2、java虛擬機

native關鍵字

  修飾方法表示這個方法是本地方法,即虛擬機的底層C程序,如線程Thread的start方法調用的start0()方法就是一個本地方法

java內存模型

什麼是類的加載

類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,而後在堆區建立一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位於堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,而且向Java程序員提供了訪問方法區內的數據結構的接口。

 

 

類加載器並不須要等到某個類被「首次主動使用」時再加載它,JVM規範容許類加載器在預料某個類將要被使用時就預先加載它,若是在預先加載的過程當中遇到了.class文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError錯誤)若是這個類一直沒有被程序主動使用,那麼類加載器就不會報告錯誤

從更高的一個維度再次來看JVM和系統調用之間的關係

JVM內存結構主要有三大塊:堆內存、方法區和棧。堆內存是JVM中最大的一塊由年輕代和老年代組成,而年輕代內存又被分紅三部分,Eden空間、From Survivor空間、To Survivor空間,默認狀況下年輕代按照8:1:1的比例來分配;

方法區存儲類信息、常量、靜態變量等數據,是線程共享的區域,爲與Java堆區分,方法區還有一個別名Non-Heap(非堆);棧又分爲java虛擬機棧和本地方法棧主要用於方法的執行。

經過一張圖來了解如何經過參數來控制各區域的內存大小

 

 

控制參數
-Xms設置堆的最小空間大小。

-Xmx設置堆的最大空間大小。

-XX:NewSize設置新生代最小空間大小。

-XX:MaxNewSize設置新生代最大空間大小。

-XX:PermSize設置永久代最小空間大小。

-XX:MaxPermSize設置永久代最大空間大小。

-Xss設置每一個線程的堆棧大小。

沒有直接設置老年代的參數,可是能夠設置堆空間大小和新生代空間大小兩個參數來間接控制。老年代空間大小=堆空間大小-年輕代大空間大小

何時觸發垃圾回收

1.當程序在新生代申請內存失敗時會觸發垃圾回收,回收的過程:把eden區還存活的對象放到S1(倖存區1),若是S1已結滿了就把S1還存活的對象放到S2(倖存區2),S2滿了就把倖存的對象放到老年代,老年代滿了就把倖存的對象放到持久代

2.當老年代或者持久代內存滿了就觸發full gc,在代碼裏面顯示的使用System.gc()也會觸發full gc,full gc會使應用程序變得很慢,因此要儘可能避免full gc,jvm調優的本質就是減小full gc

3、BIO、NIO、AIO

BIO:blocking IO,阻塞IO

NIO:non-blocking IO,非阻塞IO,爲全部的原始類型(boolean類型除外)提供緩存支持的數據容器,使用它能夠提供非阻塞式的高伸縮性網絡。

AIO:asynchronous-non-blocking IO:異步非阻塞IO

1.基本概念引入

 

同步:用戶觸發IO操做,你發起了請求就得等着對方給你返回結果,你不能走,針對調用方的,你發起了請求你等

異步:觸發觸發了IO操做,即發起了請求之後能夠作本身的事,等處理完之後會給你返回處理完成的標誌,針對調用方的,你發起了請求你不等

阻塞:你調用我,我試圖對文件進行讀寫的時候發現沒有可讀寫的文件,個人程序就會進入等待狀態,等能夠讀寫了,我處理完給你返回結果,這裏的等待和同步的等待有很大的區別,針對服務提供方的,你調用我我發現服務不可用我等

非阻塞:你調用我,我試圖對文件讀寫的時候發現沒有讀寫的文件,不等待直接返回,等我發現能夠讀寫文件處理完了再給你返回成功標誌,針對服務提供方的,你調用我我不等,我處理完了給你返回結果

二、Java對BIO、NIO、AIO的支持:
Java BIO :  同步阻塞:你調用我,你等待我給你返回結果,我發現沒有可讀寫的資源我也等待,兩個一塊兒等,JDK1.4之前的惟一選擇, 適用於數目比較少而且比較固定的架構,對服務器資源要求比較高,你們都在等資源,等服務提供方處理完了再給你返回結果
Java NIO : 同步非阻塞: 你調用我,你等待我給你返回結果,我發現沒有能夠讀寫的資源,我不等待先直接返回,等我發現有能夠讀寫的資源之後處理完給你返回結果, 適用於鏈接數目多且鏈接時間比較短(輕操做)的架構,好比聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。
Java AIO(NIO.2) :  異步非阻塞:你調用我,你不等待繼續作本身的事,我發現沒有能夠讀寫的資源,我也不等待繼續作我本身的事,等有能夠讀寫的資源的時候我處理完給你返回結果, 適用於鏈接數目多且鏈接時間比較長(重操做)的架構,好比相冊服務器,充分調用OS參與併發操做,編程比較複雜,JDK7開始支持。
三、BIO、NIO、AIO適用場景分析:
BIO方式適用於鏈接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4之前的惟一選擇,但程序直觀簡單易理解。
NIO方式適用於鏈接數目多且鏈接比較短(輕操做)的架構,好比聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。
AIO方式使用於鏈接數目多且鏈接比較長(重操做)的架構,好比相冊服務器,充分調用OS參與併發操做,編程比較複雜,JDK7開始支持。
 
另外,I/O屬於底層操做,須要操做系統支持,併發也須要操做系統的支持,因此性能方面不一樣操做系統差別會比較明顯。

4、數據庫

樂觀鎖和悲觀鎖

樂觀鎖:

樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖假設認爲數據通常狀況下不會形成衝突,因此在數據進行提交更新的時候,纔會正式對數據的衝突與否進行檢測,若是發現衝突了,則讓返回用戶錯誤的信息,讓用戶決定如何去作

相對於悲觀鎖,在對數據庫進行處理的時候,樂觀鎖並不會使用數據庫提供的鎖機制。通常的實現樂觀鎖的方式就是記錄數據版本

實現數據版本有兩種方式,第一種是使用版本號,第二種是使用時間戳

備註:

數據版本,爲數據增長的一個版本標識。當讀取數據時,將版本標識的值一同讀出,數據每更新一次,同時對版本標識進行更新。當咱們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的版本標識進行比對,若是數據庫表當前版本號與第一次取出來的版本標識值相等,則予以更新,不然認爲是過時數據。

使用版本號實現樂觀鎖

使用版本號時,能夠在數據初始化時指定一個版本號,每次對數據的更新操做都對版本號執行+1操做。並判斷當前版本號是否是該數據的最新的版本號。

1 1.查詢出商品信息
2 select (status,status,version) from t_goods where id=#{id}
3 2.根據商品信息生成訂單
4 3.修改商品status爲2
5 update t_goods 
6 set status=2,version=version+1
7 where id=#{id} and version=#{version};

 

樂觀鎖優勢與不足

樂觀併發控制相信事務之間的數據競爭(data race)的機率是比較小的,所以儘量直接作下去,直到提交的時候纔去鎖定,因此不會產生任何鎖和死鎖。但若是直接簡單這麼作,仍是有可能會遇到不可預期的結果,例如兩個事務都讀取了數據庫的某一行,通過修改之後寫回數據庫,這時就遇到了問題。

悲觀鎖:

悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度(悲觀),所以,在整個數據處理過程當中,將數據處於鎖定狀態。 悲觀鎖的實現,每每依靠數據庫提供的鎖機制 (也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)

在數據庫中,悲觀鎖的流程以下:

在對任意記錄進行修改前,先嚐試爲該記錄加上排他鎖(exclusive locking)。

若是加鎖失敗,說明該記錄正在被修改,那麼當前查詢可能要等待或者拋出異常。 具體響應方式由開發者根據實際須要決定。

若是成功加鎖,那麼就能夠對記錄作修改,事務完成後就會解鎖了。

其間若是有其餘對該記錄作修改或加排他鎖的操做,都會等待咱們解鎖或直接拋出異常。

在查詢語句後面增長LOCK IN SHARE MODE,Mysql會對查詢結果中的每行都加共享鎖,當沒有其餘線程對查詢結果集中的任何一行使用排他鎖時,能夠成功申請共享鎖,不然會被阻塞。其餘線程也能夠讀取使用了共享鎖的表,並且這些線程讀取的是同一個版本的數據。

MySQL InnoDB中使用悲觀鎖

要使用悲觀鎖,咱們必須關閉mysql數據庫的自動提交屬性,由於MySQL默認使用autocommit模式,也就是說,當你執行一個更新操做後,MySQL會馬上將結果進行提交。set autocommit=0;

 1 //0.開始事務
 2 begin;/begin work;/start transaction; (三者選一就能夠)
 3 //1.查詢出商品信息
 4 select status from t_goods where id=1 for update;
 5 //2.根據商品信息生成訂單
 6 insert into t_orders (id,goods_id) values (null,1);
 7 //3.修改商品status爲2
 8 update t_goods set status=2;
 9 //4.提交事務
10 commit;/commit work;

 

 

悲觀鎖優勢與不足

悲觀併發控制其實是「先取鎖再訪問」的保守策略,爲數據處理的安全提供了保證。可是在效率方面,處理加鎖的機制會讓數據庫產生額外的開銷,還有增長產生死鎖的機會;另外,在只讀型事務處理中因爲不會產生衝突,也不必使用鎖,這樣作只能增長系統負載;還有會下降了並行性,一個事務若是鎖定了某行數據,其餘事務就必須等待該事務處理完才能夠處理那行數

知識拓展:

1.排他鎖

排他鎖又稱寫鎖,若是事務T對數據A加上排他鎖後,則其餘事務不能再對A加任任何類型的封鎖。獲准排他鎖的事務既能讀數據,又能修改數據。

用法:

SELECT ... FOR UPDATE;

在查詢語句後面增長FOR UPDATE,Mysql會對查詢結果中的每行都加排他鎖,當沒有其餘線程對查詢結果集中的任何一行使用排他鎖時,能夠成功申請排他鎖,不然會被阻塞。

2. 共享鎖

共享鎖又稱讀鎖,是讀取操做建立的鎖。其餘用戶能夠併發讀取數據,但任何事務都不能對數據進行修改(獲取數據上的排他鎖),直到已釋放全部共享鎖。

若是事務T對數據A加上共享鎖後,則其餘事務只能對A再加共享鎖,不能加排他鎖。獲准共享鎖的事務只能讀數據,不能修改數據。

用法:

SELECT ... LOCK IN SHARE MODE;

在查詢語句後面增長LOCK IN SHARE MODE,Mysql會對查詢結果中的每行都加共享鎖,當沒有其餘線程對查詢結果集中的任何一行使用排他鎖時,能夠成功申請共享鎖,不然會被阻塞。其餘線程也能夠讀取使用了共享鎖的表,並且這些線程讀取的是同一個版本的數據。

樂觀鎖:修改數據庫記錄前,給數據庫的記錄加上一個版本號version,修改時把版本號加1,兩我的若是同時修改數據,第一我的已經把version加1了,第二我的就不能再修改了,併發數比較少時用數據庫的樂觀鎖和悲觀鎖便可知足需求,併發數比較大時就redis的隊列來解決

5、服務間的通訊

webservice對應的wsdl返回的數據是xml格式的,

restfull風格的對應的wdl,返回的數據是json的,輕量級的,如今比較經常使用,去wsdl留wdl

webservice和restfull的底層實現都是socket只是實現方式不同而已

六,數據結構和算法

數據結構:hashmap和list底層原理

算法:堆棧,快速排序,遞歸算法

相關文章
相關標籤/搜索