2017年高頻率的網際網路校園招聘面試題

2016-10-07    分類:其他、程式設計開發、首頁精華0人評論發表於2016-10-07

本文由碼農網 – 陳小波原創,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃

前言

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

Java 一些知識點

Object 有哪些方法

  • public 方法:getClassequalshashCodetoStringwaitnotify
  • protected 方法:clonefinalize
  • 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;
    }
}

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;   
    }  
}

為什麼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 裡面的四個方法onPreExecutedoInBackgroundonProgressUpdateonPostExecute

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

介面卡模式

分為兩種:類的介面卡模式、物件的介面卡模式

Android 裡的 ListView 和 RecyclerViewsetAdapter()方法就是使用了介面卡模式。

觀察者模式

在 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);
    }
}

手寫程式碼題

快速排序

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

// 排序範圍 [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;
}

撲克牌隨機發牌

對於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);
    }
}

智力題

金條付費

你讓工人為你工作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
本文作者:碼農網 – 陳小波
原創作品,轉載必須在正文中標註並保留原文連結和作者等資訊。]

相關文章