一、項目問題
1 做了哪些項目
2 使用什么技術
3 哪個是你主導的項目,一共開發多少個接口,項目多長時間,數據庫有多少個表
二、技術問題
1 用自己擅長的語言實現非遞歸單鏈表反轉 現場手寫
2 Hadoop和spark的主要區別
3 Hadoop中一個大文件進行排序,如何保證整體有序?sort只會保證單個節點的數據有序
4 Hive中有哪些udf
5 Hadoop中文件put get的過程詳細描述
6 Java中有哪些GC算法
7 Java中的弱引用 強引用和軟引用分別在哪些場景中使用
三、技術問題解析
1 用java實現非遞歸單鏈表反轉
思路:
因為在對鏈表進行反轉的時候,需要更新每一個node的“next”值,但是,在更新 next 的值前,我們需要保存 next 的值,否則我們無法繼續。所以,我們需要兩個指針分別指向前一個節點和后一個節點,每次做完當前節點“next”值更新后,把兩個節點往下移,直到到達最后節點。
實現代碼如下:
class Node { char value; Node next; } public Node reverse(Node current) { //initialization Node previousNode = null; Node nextNode = null; while (current != null) { //save the next node nextNode = current.next; //update the value of "next" current.next = previousNode; //shift the pointers previousNode = current; current = nextNode; } return previousNode; } public class Test{ public static void main(String[] args) { Node head = new Node(0); Node node1 = new Node(1); Node node2 = new Node(2); Node node3 = new Node(3); head.setNext(node1); node1.setNext(node2); node2.setNext(node3); // 打印反轉前的鏈表 Node h = head; while (null != h) { System.out.print(h.getData() + " "); h = h.getNext(); } // 調用反轉方法 // head = reverse1(head); head = reverse(head); System.out.println("\n**************************"); // 打印反轉后的結果 while (null != head) { System.out.print(head.getData() + " "); head = head.getNext(); } }
}
2 Hadoop和spark的主要區別-這個問題基本都會問到
記住3點最重要的不同之處:
- spark消除了冗余的 HDFS 讀寫: Hadoop 每次 shuffle 操作后,必須寫到磁盤,而 Spark 在 shuffle 后不一定落盤,可以 cache 到內存中,以便迭代時使用。如果操作復雜,很多的 shufle 操作,那么 Hadoop 的讀寫 IO 時間會大大增加,也是 Hive 更慢的主要原因了。
- spark消除了冗余的 MapReduce 階段: Hadoop 的 shuffle 操作一定連着完整的 MapReduce 操作,冗余繁瑣。而 Spark 基於 RDD 提供了豐富的算子操作,且 reduce 操作產生 shuffle 數據,可以緩存在內存中。
- JVM 的優化: Hadoop 每次 MapReduce 操作,啟動一個 Task 便會啟動一次 JVM,基於進程的操作。而 Spark 每次 MapReduce 操作是基於線程的,只在啟動 Executor 是啟動一次 JVM,內存的 Task 操作是在線程復用的。每次啟動 JVM 的時間可能就需要幾秒甚至十幾秒,那么當 Task 多了,這個時間 Hadoop 不知道比 Spark 慢了多。
3 Hadoop中一個大文件進行排序,如何保證整體有序
在Spark中使用算子sortByKey()可以實現按關鍵字排序,那Hadoop中實現全排序呢?
我們知道Mapreduce框架在feed數據給reducer之前會對map output key排序,這種排序機制保證了每一個reducer局部有序,hadoop 默認的partitioner是HashPartitioner,它依賴於output key的hashcode,使得相同key會去相同reducer,但是不保證全局有序,如果想要獲得全局排序結果(比如獲取top N, bottom N),Hadoop提供TotalOrderPartitioner類用於實現全局排序的功能,並且解決了OOM和數據傾斜的問題。TotalOrderPartitioner 類提供了三個采樣器,分別是:
- SplitSampler 分片采樣器,從數據分片中采樣數據,該采樣器不適合已經排好序的數據
- RandomSampler隨機采樣器,按照設置好的采樣率從一個數據集中采樣
- IntervalSampler間隔采樣機,以固定的間隔從分片中采樣數據,對於已經排好序的數據效果非常好。
具體實現可以參考https://zhuanlan.zhihu.com/p/43851100
4 Hive中有哪些UDF
Hive中有3種UDF:
- UDF(User-Defined-Function)用戶自定義函數,輸入一個數據然后產生一個數據;
- UDAF(User-Defined Aggregation Function)用戶自定義聚合函數,多個輸入數據然后產生一個輸出參數;
- UDTF(User-Defined Table-generating Function)用戶自定義表生成函數,輸入一行數據生成N行數據。
你寫過哪些UDF?在哪種情況下會使用該UDF?--自己可以擴展這個問題
5 Hadoop中數據讀寫流程分析,即文件在put get過程中具體發生了什么
i) hadoop fs -put 操作為例:
-
當接收到 PUT 請求時,嘗試在 NameNode 中 create 一個新的 INode 節點,這個節點是根據 create 中發送過去的 src 路徑構建出的目標節點,如果發現節點已存在或是節點的 parent 存在且不為 INodeDirectory 則異常中斷,否則則返回包含 INode 信息的 HdfsFileStatus 對象。
-
使用 HdfsFileStatus 構造一個實現了 OutputStream 接口的 DFSOutputStream 類,通過 nio 接口將需要傳輸的數據寫入 DFSOutputStream。
-
在 DFSOutputStream 中寫入的數據被以一定的 size(一般是 64 k)封裝成一個 DFSPacket,壓入 DataStreamer 的傳輸隊列中。
-
DataStreamer 是 Client 中負責數據傳輸的獨立線程,當發現隊列中有 DFSPacket 時,先通過 namenode.addBlock 從 NameNode 中獲取可供傳輸的 DataNode 信息,然后同指定的 DataNode 進行數據傳輸。
-
DataNode 中有一個專門的 DataXceiverServer 負責接收數據,當有數據到來時,就進行對應的 writeBlock 寫入操作,同時如果發現還有下游的 DataNode 同樣需要接收數據,就通過管道再次將發來的數據轉發給下游 DataNode,實現數據的備份,避免通過 Client 一次進行數據發送。
整個操作步驟中的關鍵步驟有 NameNode::addBlock 以及 DataNode::writeBlock, 接下來會對這兩步進行詳細分析。
ii) hadoop fs -get操作:
GET 操作的流程,相對於 PUT 會比較簡單,先通過參數中的來源路徑從 NameNode 對應 INode 中獲取對應的 Block 位置,然后基於返回的 LocatedBlocks 構造出一個 DFSInputStream 對象。在 DFSInputStream 的 read 方法中,根據 LocatedBlocks 找到擁有 Block 的 DataNode 地址,通過 readBlock 從 DataNode 獲取字節流。
6 Java中有哪些GC算法
現代虛擬機中的垃圾搜集算法:
- 標記-清除
- 復制算法(適合新生代)
- 標記-壓縮(適合老年代)
- 分代收集(新生代采用復制算法,老年代采用標記-壓縮算法)
標記 -清除算法
“標記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統一回收掉所有被標記的對象。之所以說它是最基礎的收集算法,是因為后續的收集算法都是基於這種思路並對其缺點進行改進而得到的。
它的主要缺點有兩個:一個是效率問題,標記和清除過程的效率都不高;另外一個是空間問題,標記清除之后會產生大量不連續的內存碎片,空間碎片太多可能會導致,當程序在以后的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
復制算法
“復制”(Copying)的收集算法,它將可用內存按容量划分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。
這樣使得每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。只是這種算法的代價是將內存縮小為原來的一半,持續復制長生存期的對象則導致效率降低。
標記-壓縮算法
復制收集算法在對象存活率較高時就要執行較多的復制操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
根據老年代的特點,有人提出了另外一種“標記-整理”(Mark-Compact)算法,標記過程仍然與“標記-清除”算法一樣,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存
分代收集算法
GC分代的基本假設:絕大部分對象的生命周期都非常短暫,存活時間短。
“分代收集”(Generational Collection)算法,把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”算法來進行回收。
7 Java中的弱引用、強引用、軟引用和虛引用是什么,他們分別在哪些場景中使用
- 強引用(”Strong”Reference),我們平常典型編碼 Object obj=newObject() 中的obj就是強引用。通過關鍵字new創建的對象所關聯的引用就是強引用。強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當JVM 內存空間不足,JVM 寧願拋出OutOfMemoryError運行時錯誤(OOM),使程序異常終止,也不會靠隨意回收具有強引用的“存活”對象來解決內存不足的問題。只要還有強引用指向一個對象,就能表明對象還“活着”,垃圾收集器不會碰這種對象。對於一個普通的對象,如果沒有其他的引用關系,只要超出對象的生命周期范圍或者顯式地將相應(強)引用賦值為null,就是可以被垃圾收集的了,當然具體回收時機還是要看垃圾收集策略。
- 軟引用(SoftReference),是一種相對強引用弱化一些的引用,可以讓對象豁免一些垃圾收集,只有當JVM 認為內存不足時,才會去試圖回收軟引用指向的對象。JVM 會確保在拋出OutOfMemoryError之前,清理軟引用指向的對象。軟引用通常用來實現內存敏感的緩存,如果還有空閑內存,就可以暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存。軟引用可以和一個引用隊(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。后續,我們可以調用ReferenceQueue的poll()方法來檢查是否有它所關心的對象被回收。如果隊列為空,將返回一個null,否則該方法返回隊列中前面的一個Reference對象。【應用場景】:軟引用通常用來實現內存敏感的緩存。如果還有空閑內存,就可以暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存。
- 弱引用通過WeakReference類實現。弱引用的生命周期比軟引用短。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。由於垃圾回收器是一個優先級很低的線程,因此不一定會很快回收弱引用的對象。弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。【應用場景】:弱應用同樣可用於內存敏感的緩存。
- 虛引用,你不能通過它訪問對象。幻象引用僅僅是提供了一種確保對象被finalize以后,做某些事情的機制。虛引用只是用來得知對象是否被GC。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器准備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。【應用場景】:可用來跟蹤對象被垃圾回收器回收的活動,當一個虛引用關聯的對象被垃圾收集器回收之前會收到一條系統通知。
通過表格來說明一下,如下:
引用類型 |
被垃圾回收時間 |
用途 |
生存時間 |
強引用 |
從來不會 |
對象的一般狀態 |
JVM停止運行時終止 |
軟引用 |
在內存不足時 |
對象緩存 |
內存不足時終止 |
弱引用 |
在垃圾回收時 |
對象緩存 |
gc運行后終止 |
虛引用 |
任何時候 |
跟蹤對象被垃圾回收器回收的活動 |
Unknown
|
=================================================================================
原創文章,轉載請務必將下面這段話置於文章開頭處(保留超鏈接)。
本文轉發自程序媛說事兒,原文鏈接https://www.cnblogs.com/abc8023/p/10910741.html
=================================================================================