2017年高頻率的互聯網校園招聘面試題

RDVLenard 8年前發布 | 13K 次閱讀 Java 設計模式

前言

參加了2017年校招,面試了阿里、百度、騰訊、滴滴、美團、網易、去哪兒等公司,個人是客戶端 Android 方向,總結了面試過程中頻率出現較高的題目,希望對大家有所幫助。

Java 一些知識點

Object 有哪些方法

  • public  方法: getClass 、 equals 、 hashCode 、 toString 、 wait 、 notify
  • protected  方法: clone 、 finalize
  • private  方法: registerNatives ,該方法作用是將不同平臺C/C++實現的方法映射到Java中的 native 方法
public class Object {
    private static native void registerNatives();
    // 聲明后有緊接靜態代碼塊
    static {
        registerNatives();
    }
}

自動裝箱

public static void main(String[] args) {
    int i = 0;
    Integer j = new Integer(0);
    System.out.println(j == i);
    System.out.println(j.equals(i));
}

上述代碼的輸出是

true 
true

Java 虛擬機 GC 根節點的選擇

Java通過可達性分析來判斷對象是否存活。基本思想是通過一系列稱為”GC roots”的對象作為起始點,可以作為根節點的是:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 本地方法棧中 JNI(即一般說的 Native 方法)引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象

筆者這么理解,作為GC Roots的節點主要在全局性的引用(例如常量或類靜態屬性)與執行上下文(例如棧幀中的本地變量表)中。

虛擬機棧、本地方法棧這都是局部變量,某個方法執行完,某些局部使用的對象可以被回收。

類加載機制

  • 啟動類加載器( Bootstrap ClassLoader)啟動類加載器無法被 java 程序員 直接引用, 這個類加載器負責把存放在 <JAVA_HOME>\lib 目錄中的, 或者被 -Xbootclasspath 參數指定路徑中的, 并且是被虛擬機識別的類庫加載到虛擬機內存中.
  • 擴展類加載器(Extension ClassLoader)負責加載在 <JAVA_HOME>\lib\ext 目錄中的, 或者被 java.ext.dirs 系統變量所指定的路徑中的所有類庫。
  • 應用程序類加載器( Application ClassLoader )這個類加載器是 ClassLoader  中的  getSystemClassLoader() 方法的返回值, 一般稱其為系統類加載器, 它負責加載用戶類路徑(  ClassPath  )上所指定的類庫

從 java 虛擬機的角度而降, 只存在兩種不同的類加載器:

  • 一個是啟動類加載器(  Bootstrap ClassLoader  ), 這個類加載使用 C++ 語言實現, 是虛擬機自身的一部分;
  • 另一種是其他所有的類加載器, 他們由 java 語言實現, 獨立于虛擬機之外, 并且全部繼承自 java.lang.ClassLoader

加載類的尋找范圍就是 JVM 默認路徑加上 Classpath , 類具體是使用哪個類加載器不確定。

類加載主要步驟

  • 加載 把 class 文件的二進制字節流加載到 jvm 里面
  • 驗證 確保 class 文件的字節流包含的信息符合當前 jvm 的要求 有文件格式驗證, 元數據驗證, 字節碼驗證, 符號引用驗證等
  • 準備 正式為類變量分配內存并設置類變量初始值的階段, 初始化為各數據類型的零值
  • 解析 把常量值內的符號引用替換為直接引用的過程
  • 初始化 執行類構造器 <clinit>() 方法
  • 使用 根據相應的業務邏輯代碼使用該類
  • 卸載 類從方法區移除

雙親委派模型

除了頂層的啟動類加載器之外, 其余的類加載器都應當有自己的父類加載器, 父子關系這兒一般都是以組合來實現。

工作過程: 如果一個類加載器收到了類加載的請求, 它首先不會自己去嘗試加載這個類, 而是把這個請求委派給父類加載器去完成, 最終所有的加載請求都會傳送到頂層的啟動類加載器中, 只有當父類加載器反饋自己無法完成這個請求時候, 才由子加載器來加載。

例如類 Object ,它放在 rt.jar 中,無論哪一個類加載器要加載這個類,最終都是委派給啟動類加載器進行加載,因此 Object 類在程序的各種類加載器環境中都是同一個類。

對于任何一個類, 都需要由加載它的類加載器和這個類本身一同確定其在 java 虛擬機中的唯一性。

ClassLoader.loadClass() 的代碼如下,先檢查是否已經被加載過,如果沒有則 parent.loadClass() 調用父加載器的 loadClass() 方法,如果父加載器為空則默認使用啟動類加載器作為父加載器。如果父類加載器加載失敗,拋出 ClassNotFoundException ,再調用自己的 findClass() 方法進行加載。

另外,如果我們自己實現類加載器,一般是 Override 復寫  findClass 方法,而不是 loadClass 方法。

protected Class<?> loadClass(String name, boolean resolve) 
throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

        if (c == null) {
            // If still not found, then invoke findClass in order
            // to find the class.
            long t1 = System.nanoTime();
            c = findClass(name); //可以Override該方法
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}

}</code></pre>

Java 后臺的一點知識

JSP 與 Servlet 的關系

  • Tomcat 等 Web 容器最終會把 JSP轉化為 Servlet
  • Jsp更擅長表現于頁面顯示, Servlet更擅長于邏輯控制
  • Servlet是利用  System.out.println() 來輸出 html 代碼,由于包括大量的HTML標簽、大量的靜態文本及格式等,導致Servlet的開發效率低下
  • JSP通過在標準的HTML頁面中嵌入Java代碼,其靜態的部分無須Java程序控制,Java 代碼只控制那些動態生成的信息
  • 最終 JSP 被容器解釋為 Servlet,其中Html 代碼也是用 System.out.println() 等拼接輸出的
  • JSP 第一次訪問的時候,要轉化為 java 文件,然后編譯為 class 文件,所以第一次訪問 JSP 速度會比較慢,后面會快很多

Servlet 生命周期

主要是 java.servlet.Servlet 接口中的 init() 、 service() 、和 destroy() 3個方法。

  • 初始化階段,web容器通過調用 init() 方法來初始化 Servlet 實例,在Servlet的整個生命周期類, init() 方法只被調用一次
  • 客戶請求到來時,容器會開始一個新線程,并調用servlet的  service() 方法, service()  方法根據請求的http方法來調用  doget()  或 dopost()
  • 終止階段調用 destroy() 方法,銷毀一些資源

GET 請求 vs POST 請求

  • GET 用于信息獲取,是安全的和冪等的, GET 一般是對后臺數據庫的信息進行查詢
  • POST 表示可能修改變服務器上的資源的請求,一般是對后臺數據庫進行增、刪、改的操作
  • GET 請求的參數會跟在URL后進行傳遞,請求的數據會附在URL之后,以 ? 分割URL和傳輸數據,參數之間以 & 相連,一般瀏覽器對 URL 的長度會有限制
  • POST 請求,提交的數據則放置在是HTTP包的包體中,用類似 Key-Value 的格式發送一些數據,相對來說, GET 請求會把請求的參數暴露在 URL 中,安全性比 POST 差一些

HTTP 請求的基本格式

  • < request line>  請求行
  • <headers>  請求頭(參數頭)
  • <blank line>  空白行
  • [<request-body>]  請求實體( GET沒有, POST有 )

數據庫

索引的分類

主要分為聚集索引和非聚集索引:

  • 聚集索引存儲記錄物理上連續,而非聚集索引是邏輯上的連續,物理存儲并不連續
  • 聚集索引一個表只能有一個,而非聚集索引一個表可以存在多個

ResultSet 統計記錄數目

Java 中使用 JDBC 連接數據庫,最后都會得到一個 ResultSet,比如如下的代碼

Connection con = DriverManager.getConnection(url, username, password);
 Statement sta = con.createStatement();
 String sql = "select * from student";
 ResultSet resultSet = sta.executeQuery(sql);

那么如何根據得到的 ResultSet 統計一共有多少條記錄呢?注意: ResultSet 沒有提供類似 size() 、 length 的 API 來直接獲取總記錄數。

方法1:利用循環

int sum = 0;      
while(resultSet.next()){      
    sum++;      
}

方法2:利用ResultSet的getRow方法來獲得ResultSet的總行數

resultSet.last(); //移到最后一行     
int rowCount = resultSet.getRow(); //得到當前行號,也就是記錄數

設計模式

單例模式

單例模式中必須保證只有一個實例存在。有時候單例是為了避免重復創建多個實例造成資源浪費,有時候也是為了避免多個不同的實例導致系統不一致的行為。

Android 中,App啟動時系統會創建一個 Application 對象,用來存儲系統的一些信息,這兒的 Application 就是是單例模式的應用。可以通過 Context.getApplicationContext() 獲取唯一的 Application 實例。

class Singleton {
private volatile static Singleton instance;

private Singleton() { }   

public static Singleton getInstance() {   
    //第一重判斷  
    if (instance == null) {  
        //鎖定代碼塊  
        synchronized (Singleton.class) {  
            //第二重判斷  
            if (instance == null) {  
                instance = new Singleton(); //創建單例實例  
            }  
        }  
    }  
    return instance;   
}  

}</code></pre>

為什么 synchronized 里面需要加一次判斷 if (instance == null) ,是考慮這樣的特殊情形:比如線程A、B都到達第一個 if (instance == null) ,線程A進入 synchronized 代碼中創建實例,線程B排隊等待。但當A執行完畢時,線程B進入 synchronized 鎖定代碼,它并不知道實例已經創建,將繼續創建新的實例,導致產生多個單例對象。

也可以用內部類的方式創建,

public class Singleton(){
    private static class Inner {
        private static Singleton instance = new Singleton();
    }
    private Singleton() {
    }
    public static Singleton getInstance(){
        return Inner.instance;
    }
}

模板方法模式

在父類中實現一個算法不變的部分,并將可變的行為留給子類來實現。

比如 AsyncTask 里面的四個方法 onPreExecute 、 doInBackground 、 onProgressUpdate 、 onPostExecute

還有 Activity 也應用了模板方法模式

onCreate 、 onStart 、 onResume 、 onPause 、 onStop 、 onDestroy 、 onRestart

適配器模式

分為兩種:類的適配器模式、對象的適配器模式

Android 里的 ListView 和  RecyclerView 的 setAdapter() 方法就是使用了適配器模式。

觀察者模式

在 GUI 中,不管是 Windows 桌面應用、或者 Android、IOS,都會給某個按鈕 Button 設置監聽事件,這兒就是使用了觀察者模式。Android 中設置 Button 的監聽事件代碼如下:

final Button button = (Button) findViewById(R.id.button_id);
button.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        // Perform action on click
    }
});

筆試編程題

線程 VS 進程

關于線程和進程,不正確的描述是 __ 。(選 D 棧是線程私有, 保存其運行狀態和局部變量 )

A. 進程的隔離性要好于線程

B. 線程在資源消耗上通常要比進程輕量

C. 不同進程間不會共享邏輯地址空間

D. 同一個進程的線程之間共享內存,包括堆和棧

E. 進程間有途徑共享大量內存中的數據

F. 線程間通訊可以通過直接訪問全局變量,或者使用進程間通訊的機制(IPC)

找出未打卡的員工

題目:輸入兩行數據,第一行為全部員工的 id,第二行為某一天打卡的員工 id,已知只有一個員工沒有打卡,求出未打卡員工的 id。(員工 id 不重復,每行輸入的 id 未排序)

輸入:

1001 1003 1002 1005 1004

1002 1003 1001 1004

輸出:

1005

分析:可以用兩個 List,第一個 List 保存所有員工的 id,第二個 List 保存打卡員工的 id,從第一個List 中把第二個 List 的數據都刪除,最終剩下的就是未打卡員工的 id。

更好的方法:異或,兩行數據中未打卡員工的 id 出現了一次,其余員工的 id 都出現了2次,兩個相同的數異或為0。

public static void main(String[] args) {
    Scanner scan = new Scanner(System.in);
    while (scan.hasNext()) {

    String[] ids = scan.nextLine().split(" ");
    String[] marks = scan.nextLine().split(" ");
    int result = 0;
    for (int i = 0; i < ids.length; i++) {
        result ^= Integer.parseInt(ids[i]);
    }
    for (int i = 0; i < marks.length; i++) {
        result ^= Integer.parseInt(marks[i]);
    }
    System.out.println(result);
}

}</code></pre>

手寫代碼題

快速排序

排序是經典面試題,公司也希望通過手寫快排來考察面試者的編程習慣和基本功。

// 排序范圍 [start, end], 包含 end
public void sort(int[] arr, int start, int end) {
    if (start < end) {
        int p = partition(arr, start, end);
        quickSort(arr, start, p - 1);
        quickSort(arr, p + 1, end);
    }
}
// 一次劃分代碼,返回被劃分后的基準位置
public static int partition(int[] arr, int left, int right) {
    int pivot = arr[left]; 
    while (left < right) {
        while (left < right && arr[right] >= pivot)
            right--;
        if (left < right)
            arr[left++] = arr[right];
        while (left < right && arr[left] <= pivot)
            left++;
        if (left < right)
            arr[right--] = arr[left];
    }
    arr[left] = pivot;
    return left;
}

Note:快排是不穩定的,常見的穩定排序是:冒泡、插入、歸并

括號字符串是否合法

某個字符串只包括 ( 和 ) ,判斷其中的括號是否匹配正確,比如 (()()) 正確, ((())() 錯誤, 不允許使用棧 。

這種類似題的常見思路是棧,對于左括號入棧,如果遇到右括號,判斷此時棧頂是不是左括號,是則將其出棧,不是則該括號序列不合法。

面試官要求不能使用棧,可以使用計數器,利用 int count 字段。

public static boolean checkBrackets(String str) {
    char[] cs = str.toCharArray();
    int count = 0;
    for (int i = 0; i < cs.length; i++) {
        if (cs[i] == '(')
            count++;
        else {
            count--;
            if (count < 0) {
                return false;
            }
        }
    }

return count == 0;

}</code></pre>

撲克牌隨機發牌

對于52張牌,實現一個隨機打算撲克牌順序的程序。52張牌使用 int 數組模擬。

該算法的難點是如何保證隨機性?有個經典算法 shuffle ,思路就是遍歷數組,在剩下的元素里再隨機取一個元素,然后再在剩下的元素里再隨機取一個元素。每次取完元素后,我們就不會讓這個元素參與下一次的選取。

To shuffle an array a of n elements (indices 0..n-1):
  for i from n ? 1 downto 1 do
       j ← random integer with 0 ≤ j ≤ i
       exchange a[j] and a[i]

注意這兒是 0 ≤ j ≤ i ,包括 j=i 的情況,因為可能洗牌后某個牌未發生交換,比如第51張牌還是原來的第51張牌。

public void randomCards() {
    int[] data = new int[52];
    Random random= new Random();
    for (int i = 0; i < data.length; i++)
        data[i] = i;

for (int i = data.length - 1; i > 0; i--) {
    int temp = random.nextInt(i+1); //產生 [0,i] 之間的隨機數
    swap(data,i,temp);
}

}</code></pre>

智力題

金條付費

你讓工人為你工作7天,回報是一根金條,這個金條平分成相連的7段,你必須在每天結束的時候給他們一段金條,如果只允許你兩次把金條弄斷,你如何給你的工人付費?

答案:切成一段,兩段,和四段.

第1天: 給出1.

第2天: 給出2,還回1.

第3天: 給出1.

第4天: 給出4,還回1+2.

第5天: 給出1.

第6天: 給出2,還回1.

第7天: 給出1.

賽馬

25匹馬,速度都不同,但每匹馬的速度都是定值。現在只有5條賽道,無法計時,即每賽一場最多只能知道5匹馬的相對快慢。問最少賽幾場可以找出25匹馬中速度最快的前3名?

答案:

  • 25匹馬分成5組,先進行5場比賽
  • 再將剛才5場的冠軍進行第6場比賽,得到第一名。按照第6場比賽的名詞把前面5場比賽所在的組命名為 A、B、C、D、E 組,即 A 組的冠軍是第6場第一名,B 組的冠軍是第二名 …
  • 分析第2名和第3名的可能性,如果確定有多于3匹馬比某匹馬快,那它可以被淘汰了。因為 D 組是第6場的第四名,整個D 組被淘汰了,同意整個 E 組被淘汰。剩下可能是整體的第2、3名的就是C組的第1名、B組的1、2名、A組的第2、3名。取這5匹馬進行第7場比賽
    -所以,一共需要7場比賽

 

來自:http://www.codeceo.com/article/it-interview-question-2017.html

 

 本文由用戶 RDVLenard 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!