1. 程式人生 > >2018初冬阿里巴巴面試題——(部分一)

2018初冬阿里巴巴面試題——(部分一)

1.開發中用了比較多的資料結構有哪些?

 

原貼:https://blog.csdn.net/qq_31615049/article/details/80545713

2.談談你對HashMap的理解,底層的基本實現。HashMap怎麼解決碰撞的問題的?

理解:

hashMap是基於雜湊表的map介面的非同步實現,相對hashtable來說,是hashtable的輕量級的實現.

允許null值的出現,通過鍵值對來儲存,主要通過get和put來操作資料的插入和查詢

基本實現:

HashMap的主幹是一個Entry陣列。Entry是HashMap的基本組成單元,每一個Entry包含一個key-value鍵值對。 

簡單來說,HashMap由陣列+連結串列組成的,陣列是HashMap的主體,連結串列則是主要為了解決雜湊衝突而存在的,如果定位到的陣列位置不含連結串列(當前entry的next指向null),那麼對於查詢,新增等操作很快,僅需一次定址即可;如果定位到的陣列包含連結串列,對於新增操作,其時間複雜度為O(n),首先遍歷連結串列,存在即覆蓋,否則新增;對於查詢操作來講,仍需遍歷連結串列,然後通過key物件的equals方法逐一比對查詢。所以,效能考慮,HashMap中的連結串列出現越少,效能才會越好。

碰撞問題:

1.碰撞的產生

 Hashmap裡面的bucket出現了單鏈表的形式,散列表要解決的一個問題就是雜湊值的衝突問題,通常是兩種方法:連結串列法和開放地址法。連結串列法就是將相同hash值的物件組織成一個連結串列放在hash值對應的槽位;開放地址法是通過一個探測演算法,當某個槽位已經被佔據的情況下繼續查詢下一個可以使用的槽位。java.util.HashMap採用的連結串列法的方式,連結串列是單向連結串列。形成單鏈表的核心程式碼如下:

void addEntry(int hash, K key, V value, int bucketIndex) {

Entry<K,V> e = table[bucketIndex];

table[bucketIndex] = new Entry<K,V>(hash, key, value, e);

if (size++ >= threshold)

resize(2 * table.length);

}

   上面方法的程式碼很簡單,但其中包含了一個設計:系統總是將新新增的 Entry 物件放入 table 陣列的 bucketIndex 索引處——如果 bucketIndex 索引處已經有了一個 Entry 物件,那新新增的 Entry 物件指向原有的 Entry 物件(產生一個 Entry 鏈),如果 bucketIndex 索引處沒有 Entry 物件,也就是上面程式程式碼的 e 變數是 null,也就是新放入的 Entry 物件指向 null,也就是沒有產生 Entry 鏈。 HashMap裡面沒有出現hash衝突時,沒有形成單鏈表時,hashmap查詢元素很快,get()方法能夠直接定位到元素,但是出現單鏈表後,單個bucket 裡儲存的不是一個 Entry,而是一個 Entry 鏈,系統只能必須按順序遍歷每個 Entry,直到找到想搜尋的 Entry 為止——如果恰好要搜尋的 Entry 位於該 Entry 鏈的最末端(該 Entry 是最早放入該 bucket 中),那系統必須迴圈到最後才能找到該元素。

   通過上面可知如果多個hashCode()的值落到同一個桶內的時候,這些值是儲存到一個連結串列中的。最壞的情況下,所有的key都對映到同一個桶中,這樣hashmap就退化成了一個連結串列——查詢時間從O(1)到O(n)。也就是說我們是通過連結串列的方式來解決這個Hash碰撞問題的。

Hash碰撞效能分析

  Java 7:隨著HashMap的大小的增長,get()方法的開銷也越來越大。由於所有的記錄都在同一個桶裡的超長連結串列內,平均查詢一條記錄就需要遍歷一半的列表。不過Java 8的表現要好許多!它是一個log的曲線,因此它的效能要好上好幾個數量級。儘管有嚴重的雜湊碰撞,已是最壞的情況了,但這個同樣的基準測試在JDK8中的時間複雜度是O(logn)。單獨來看JDK 8的曲線的話會更清楚,這是一個對數線性分佈:

Java8碰撞優化提升

   為什麼會有這麼大的效能提升,儘管這裡用的是大O符號(大O描述的是漸近上界)?其實這個優化在JEP-180中已經提到了。如果某個桶中的記錄過大的話(當前是TREEIFY_THRESHOLD = 8),HashMap會動態的使用一個專門的treemap實現來替換掉它。這樣做的結果會更好,是O(logn),而不是糟糕的O(n)。它是如何工作的?前面產生衝突的那些KEY對應的記錄只是簡單的追加到一個連結串列後面,這些記錄只能通過遍歷來進行查詢。但是超過這個閾值後HashMap開始將列表升級成一個二叉樹,使用雜湊值作為樹的分支變數,如果兩個雜湊值不等,但指向同一個桶的話,較大的那個會插入到右子樹裡。如果雜湊值相等,HashMap希望key值最好是實現了Comparable介面的,這樣它可以按照順序來進行插入。這對HashMap的key來說並不是必須的,不過如果實現了當然最好。如果沒有實現這個介面,在出現嚴重的雜湊碰撞的時候,你就並別指望能獲得性能提升了。這個效能提升有什麼用處?比方說惡意的程式,如果它知道我們用的是雜湊演算法,它可能會發送大量的請求,導致產生嚴重的雜湊碰撞。然後不停的訪問這些key就能顯著的影響伺服器的效能,這樣就形成了一次拒絕服務攻擊(DoS)。JDK 8中從O(n)到O(logn)的飛躍,可以有效地防止類似的攻擊,同時也讓HashMap效能的可預測性稍微增強了一些。

原貼:https://blog.csdn.net/luo_da/article/details/77507315

3.對JVM熟不熟悉?簡單說說類載入過程,裡面執行了哪些操作?GC和記憶體管理,平時在tomcat裡面有沒有進行過相關的配置?

關於JVM和類載入過程

1、什麼是JVM ?
JVM, 中文名是Java虛擬機器, 正如它的名字, 是一個虛擬機器器,來模擬通用的物理機。 JVM是一個標準,一套規範,  規定了.class檔案在其內部執行的相關標準和規範。 及其相關的內部構成。 比如:所有的JVM都是基於棧結構的執行方式。那麼不符合這種要求的,不算是JVM, 如Android中所使用的Dalvik 虛擬機器就不能稱作是JAVA 虛擬機器, 因為它是基於暫存器(最新的Android系統據說已經放棄了Dalvik VM, 而是使用ART)。

JVM相關的產品有很多, 通常最有名的莫過於現在Oracle公司所有的HotSpot 虛擬機器。因此, 這裡討論的都是HotSpot虛擬機器, 如果沒有特別說明。 

2、類載入?
類載入, 是通過JVM的類載入器從JVM外部以二進位制位元組流的方式載入到JVM中。但JVM本身有至少三種類載入器:BootStrap(根類載入器,C++實現, 載入位於jre/lib/rt.jar)、Extension(擴充套件類載入器, 主要用於載入jre/lib/ext/下的jar)、System(載入classpath環境變數所指定的class);當然還有,自定義的類載入器(用於實現自己的類載入器, 如Tomcat中就實現多個類載入器,用來管理不同的jar)。
如果, 我有一個HelloWorld的類需要載入, 首先類載入器會去從最底層的類載入器去驗證這個類是否被載入, 如果沒有, 則委託給上一次的類載入器驗證是否被載入, 如果到BootStrap類載入器都沒有發現HelloWorld類被載入, 那麼類載入器將執行載入任務, 如果根類載入器沒有載入, 則委託給下一級的Extension類載入器去嘗試載入,直到這個類被載入成功。 參考下圖:

需要注意的是:如果一個類被不同的類載入器載入, 那麼就是兩個不同的類。

3、類載入的具體過程?
被java編譯器(不僅限於, 還有其他任何的可以編輯成為.class的編譯器)編譯過的.class檔案(可能是以jar、war、jsp等形式), 經過類載入器載入 、 驗證、準備、解析、初始化之後, 才可以被使用。基本的過程如下:

載入: 首先,通過一個類的全類名來獲取此類的二進位制位元組流。其次,將類中所代表的靜態儲存結構轉換為執行時資料結構, 最後,生成一個代表載入的類的java.lang.Class物件, 作為方法區這個類的所有資料的訪問入口。載入完成之後, 虛擬機器外部的二進位制靜態資料結構就轉換成了虛擬機器所需要的結構儲存在方法區中(至於如何轉換, 則由具體虛擬機器自己定義實現), 而所生成的Class物件, 則存放在方法區中, 用來作為程式訪問方法區中資料的外部介面。


驗證:其目的就是保證載入進來的.class檔案不會危害到虛擬機器本身, 且內容符合當前虛擬機器規範要求。主要驗證的內容大致有:檔案格式、元資料驗證、位元組碼驗證、符號引用驗證。其中檔案格式驗證, 主要確保符合class檔案格式規範(如文字字尾為.class的檔案將驗證不通過), 以及主次版本號, 驗證是否當前JVM可以處理等。元資料驗證,主要驗證編譯後的位元組碼描述資訊是否符合java語法規範。位元組碼驗證, 其最為複雜, 主要通過控制流和資料流確定語義是否合法、符合邏輯。符號引用驗證,可以看做是除自身以外(常量池中各種引用符號)的資訊匹配校驗,如通過持有的引用能否找到對應的例項。


準備:正式為類變數分配記憶體,並設定類變數的初始值。這些變數都會在方法區中進行分配。


解析:將常量池內的符號引用替換為直接引用的過程。主要針對類或介面、欄位、類方法、介面方法、方法型別、方法控制代碼等。


初始化:載入的最後階段, 程式真正執行的開始。

原貼:https://blog.csdn.net/weiguolong0306/article/details/60324988

GC和記憶體管理

根據JVM規範,JVM把記憶體劃分瞭如下幾個區域:

1. 方法區
2. 堆區
3. 本地方法棧
4. 虛擬機器棧
5. 程式計數器 

其中,方法區和堆是所有執行緒共享的。

è¿éåå¾çæè¿°

而關於GC(垃圾回收)這裡我們要介紹幾種垃圾收集演算法

①Mark-Sweep(標記-清除)演算法

  這是最基礎的垃圾回收演算法,之所以說它是最基礎的是因為它最容易實現,思想也是最簡單的。標記-清除演算法分為兩個階段:標記階段和清除階段。標記階段的任務是標記出所有需要被回收的物件,清除階段就是回收被標記的物件所佔用的空間。

  標記-清除演算法實現起來比較容易,但是有一個比較嚴重的問題就是容易產生記憶體碎片,碎片太多可能會導致後續過程中需要為大物件分配空間時無法找到足夠的空間而提前觸發新的一次垃圾收集動作。 

  ②.Copying(複製)演算法

  為了解決Mark-Sweep演算法的缺陷,Copying演算法就被提了出來。它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活著的物件複製到另外一塊上面,然後再把已使用的記憶體空間一次清理掉,這樣一來就不容易出現記憶體碎片的問題。

  這種演算法雖然實現簡單,執行高效且不容易產生記憶體碎片,但是卻對記憶體空間的使用做出了高昂的代價,因為能夠使用的記憶體縮減到原來的一半。 很顯然,Copying演算法的效率跟存活物件的數目多少有很大的關係,如果存活物件很多,那麼Copying演算法的效率將會大大降低。我們的新生代GC演算法採用的是這種演算法

  ③Mark-Compact(標記-整理)演算法

  為了解決Copying演算法的缺陷,充分利用記憶體空間,提出了Mark-Compact演算法。該演算法標記階段和Mark-Sweep一樣,但是在完成標記之後,它不是直接清理可回收物件,而是將存活物件都向一端移動,然後清理掉端邊界以外的記憶體。在一般廠商JVM         中老年代GC就是使用的這種演算法,由於老年代的特點是每次回收都只回收少量物件。

下面有幾種建立的垃圾收集器,使用者可以根據自己的需求組合出新年代和老年代使用的收集器

新生代GC :序列GC(SerialGC)、並行回收GC(ParallelScavenge)和並行GC(ParNew)

  序列GC:在整個掃描和複製過程採用單執行緒的方式來進行,適用於單CPU、新生代空間較小及對暫停時間要求不是非常高的應用上,是client級別預設的GC方式,可以通過-XX:+UseSerialGC來強制指定。

  並行回收GC:在整個掃描和複製過程採用多執行緒的方式來進行,適用於多CPU、對暫停時間要求較短的應用上,是server級別預設採用的GC方式,可用-XX:+UseParallelGC來強制指定,用-XX:ParallelGCThreads=4來指定執行緒數。

     並行GC:與老年代的併發GC配合使用。

老年代GC:序列GC(Serial MSC)、並行GC(Parallel MSC)和併發GC(CMS)

   序列GC(Serial MSC):client模式下的預設GC方式,可通過-XX:+UseSerialGC強制指定。每次進行全部回收,進行Compact,非常耗費時間。

   並行GC(Parallel MSC):吞吐量大,但是GC的時候響應很慢:server模式下的預設GC方式,也可用-XX:+UseParallelGC=強制指定。可以在選項後加等號來制定並行的執行緒數。

   併發GC(CMS):響應比並行gc快很多,但是犧牲了一定的吞吐量。

原貼:https://blog.csdn.net/suifeng3051/article/details/48292193

ps:思考“GC是在什麼時候,對什麼東西,做了什麼事情?”

  • 什麼時候

  從字面上翻譯過來就是什麼時候觸發我們的GC機制

  ①在程式空閒的時候。這個回答無力吐槽

  ②程式不可預知的時候/手動呼叫system.gc()。關於手動呼叫不推薦

  ③Java堆記憶體不足時,GC會被呼叫。當應用執行緒在執行,並在執行過程中建立新物件,若這時記憶體空間不足,JVM就會強制地呼叫GC執行緒,以便回收記憶體用於新的分配。若GC一次之後仍不能滿足記憶體分配的要求,JVM會再進行兩次GC作進一步的嘗試,若仍無法滿足要求,則 JVM將報“out of memory”的錯誤,Java應用將停止。就是

  這時候如果你們講出新生代和老年代的話或許會更細的瞭解一下Minor GC、Full GC、OOM什麼時候觸發!

  建立物件是新生代的Eden空間呼叫Minor GC;當升到老年代的物件大於老年代剩餘空間Full GC;GC與非GC時間耗時超過了GCTimeRatio的限制引發OOM。

  • 什麼東西

  從字面的意思翻譯過來就是能被GC回收的物件都有哪些特徵

  ①超出作用域的物件/引用計數為空的物件。

  引用計數演算法:給物件中新增一個引用計數器,每當有一個地方引用它時,計數器就加1;當引用失效時,計數器值就減1;任何時刻計數器都為0的物件就是不可能再被使用的。

  ②從GC Root開始搜尋,且搜尋不到的物件

  跟搜尋演算法:以一系列名為 GC Root的物件作為起點,從這些節點開始往下搜尋,搜尋走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈的時候,則就證明此物件是不可用的。

  這裡會提出一個思考,什麼樣的物件能成為GC Root : 虛擬機器中的引用的物件、方法區中的類靜態屬性引用的物件、方法區中常量引用的物件、本地方法棧中jni的引用物件。

  ③從root搜尋不到,而且經過第一次標記、清理後,仍然沒有復活的物件。

  • 做什麼

  不同年代、不同種類的收集器很多,不過總體的作用是刪除不使用的物件,騰出記憶體空間。補充一些諸如停止其他執行緒執行、執行finalize等的說明。

ok  現在來回答一下我們最上面的問題,上面時候容易發生記憶體洩露

  ①靜態集合類像HashMap、Vector等

  ②各種連線,資料庫連線,網路連線,IO連線等沒有顯示呼叫close關閉,不被GC回收導致記憶體洩露。

  ③監聽器的使用,在釋放物件的同時沒有相應刪除監聽器的時候也可能導致記憶體洩露。

關於tomcat的相關配置,https://www.cnblogs.com/xuwc/p/8523681.html 這篇文章有詳細的介紹。

4.http協議,get和post的基本區別?tcp/ip協議,三次握手。

GET和POST是什麼?HTTP協議中的兩種傳送請求的方法。

HTTP是什麼?HTTP是基於TCP/IP的關於資料如何在全球資訊網中如何通訊的協議。

HTTP的底層是TCP/IP。所以GET和POST的底層也是TCP/IP,也就是說,GET/POST都是TCP連結。GET和POST能做的事情是一樣一樣的。你要給GET加上request body,給POST帶上url引數,技術上是完全行的通的。 

在我大全球資訊網世界中,TCP就像汽車,我們用TCP來運輸資料,它很可靠,從來不會發生丟件少件的現象。但是如果路上跑的全是看起來一模一樣的汽車,那這個世界看起來是一團混亂,送急件的汽車可能被前面滿載貨物的汽車攔堵在路上,整個交通系統一定會癱瘓。為了避免這種情況發生,交通規則HTTP誕生了。HTTP給汽車運輸設定了好幾個服務類別,有GET, POST, PUT, DELETE等等,HTTP規定,當執行GET請求的時候,要給汽車貼上GET的標籤(設定method為GET),而且要求把傳送的資料放在車頂上(url中)以方便記錄。如果是POST請求,就要在車上貼上POST的標籤,並把貨物放在車廂裡。當然,你也可以在GET的時候往車廂內偷偷藏點貨物,但是這是很不光彩;也可以在POST的時候在車頂上也放一些資料,讓人覺得傻乎乎的。HTTP只是個行為準則,而TCP才是GET和POST怎麼實現的基本。

但是,我們只看到HTTP對GET和POST引數的傳送渠道(url還是requrest body)提出了要求。“標準答案”裡關於引數大小的限制又是從哪來的呢?

 

大全球資訊網世界中,還有另一個重要的角色:運輸公司。不同的瀏覽器(發起http請求)和伺服器(接受http請求)就是不同的運輸公司。 雖然理論上,你可以在車頂上無限的堆貨物(url中無限加引數)。但是運輸公司可不傻,裝貨和卸貨也是有很大成本的,他們會限制單次運輸量來控制風險,資料量太大對瀏覽器和伺服器都是很大負擔。業界不成文的規定是,(大多數)瀏覽器通常都會限制url長度在2K個位元組,而(大多數)伺服器最多處理64K大小的url。超過的部分,恕不處理。如果你用GET服務,在request body偷偷藏了資料,不同伺服器的處理方式也是不同的,有些伺服器會幫你卸貨,讀出資料,有些伺服器直接忽略,所以,雖然GET可以帶request body,也不能保證一定能被接收到哦。

好了,現在你知道,GET和POST本質上就是TCP連結,並無差別。但是由於HTTP的規定和瀏覽器/伺服器的限制,導致他們在應用過程中體現出一些不同。 

GET和POST還有一個重大區別,簡單的說:

GET產生一個TCP資料包;POST產生兩個TCP資料包。

長的說:

對於GET方式的請求,瀏覽器會把http header和data一併傳送出去,伺服器響應200(返回資料);

而對於POST,瀏覽器先發送header,伺服器響應100 continue,瀏覽器再發送data,伺服器響應200 ok(返回資料)。

 

也就是說,GET只需要汽車跑一趟就把貨送到了,而POST得跑兩趟,第一趟,先去和伺服器打個招呼“嗨,我等下要送一批貨來,你們開啟門迎接我”,然後再回頭把貨送過去。

 

因為POST需要兩步,時間上消耗的要多一點,看起來GET比POST更有效。因此Yahoo團隊有推薦用GET替換POST來優化網站效能。但這是一個坑!跳入需謹慎。為什麼?

1. GET與POST都有自己的語義,不能隨便混用。

2. 據研究,在網路環境好的情況下,發一次包的時間和發兩次包的時間差別基本可以無視。而在網路環境差的情況下,兩次包的TCP在驗證資料包完整性上,有非常大的優點。

3. 並不是所有瀏覽器都會在POST中傳送兩次包,Firefox就只發送一次。

原貼:https://www.cnblogs.com/logsharing/p/8448446.html

tcp/ip

TCP/IP 是一類協議系統,它是用於網路通訊的一套協議集合.
傳統上來說 TCP/IP 被認為是一個四層協議


1) 網路介面層:
主要是指物理層次的一些介面,比如電纜等.

2) 網路層:
提供獨立於硬體的邏輯定址,實現實體地址與邏輯地址的轉換.

在 TCP / IP 協議族中,網路層協議包括 IP 協議(網際協議),ICMP 協議( Internet 網際網路控制報文協議),以及 IGMP 協議( Internet 組管理協議).

3) 傳輸層:
為網路提供了流量控制,錯誤控制和確認服務.

在 TCP / IP 協議族中有兩個互不相同的傳輸協議: TCP(傳輸控制協議)和 UDP(使用者資料報協議).

4) 應用層:
為網路排錯,檔案傳輸,遠端控制和 Internet 操作提供具體的應用程式


三次握手

簡單舉個例子,A(client)打電話給B(server),

第一次:A跟B說:喂,你聽得到嗎?

第二次:B跟A說:聽得到,你聽得到嗎?

第三次:A跟B說:聽得到。

這樣的三次握手之後,client跟server就可以進行正常的通話了。