參加了2017年校招,面試了阿里、百度、騰訊、滴滴、美團(tuán)、網(wǎng)易、去哪兒等公司,個(gè)人是客戶端 Android 方向,總結(jié)了面試過(guò)程中頻率出現(xiàn)較高的題目,希望對(duì)大家有所幫助。 Object 有哪些方法
registerNatives(); } } 自動(dòng)裝箱Integer j = new Integer(0); System.out.println(j == i); System.out.println(j.equals(i)); } 上述代碼的輸出是 Java 虛擬機(jī) GC 根節(jié)點(diǎn)的選擇Java通過(guò)可達(dá)性分析來(lái)判斷對(duì)象是否存活?;舅枷胧峭ㄟ^(guò)一系列稱為”GC roots”的對(duì)象作為起始點(diǎn),可以作為根節(jié)點(diǎn)的是:
筆者這么理解,作為GC Roots的節(jié)點(diǎn)主要在全局性的引用(例如常量或類靜態(tài)屬性)與執(zhí)行上下文(例如棧幀中的本地變量表)中。 虛擬機(jī)棧、本地方法棧這都是局部變量,某個(gè)方法執(zhí)行完,某些局部使用的對(duì)象可以被回收。 類加載機(jī)制
從 java 虛擬機(jī)的角度而降, 只存在兩種不同的類加載器:
加載類的尋找范圍就是 JVM 默認(rèn)路徑加上Classpath, 類具體是使用哪個(gè)類加載器不確定。 類加載主要步驟
雙親委派模型除了頂層的啟動(dòng)類加載器之外, 其余的類加載器都應(yīng)當(dāng)有自己的父類加載器, 父子關(guān)系這兒一般都是以組合來(lái)實(shí)現(xiàn)。 工作過(guò)程:如果一個(gè)類加載器收到了類加載的請(qǐng)求, 它首先不會(huì)自己去嘗試加載這個(gè)類, 而是把這個(gè)請(qǐng)求委派給父類加載器去完成, 最終所有的加載請(qǐng)求都會(huì)傳送到頂層的啟動(dòng)類加載器中, 只有當(dāng)父類加載器反饋?zhàn)约簾o(wú)法完成這個(gè)請(qǐng)求時(shí)候, 才由子加載器來(lái)加載。 例如類 Object,它放在 rt.jar 中,無(wú)論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給啟動(dòng)類加載器進(jìn)行加載,因此 Object 類在程序的各種類加載器環(huán)境中都是同一個(gè)類。 對(duì)于任何一個(gè)類, 都需要由加載它的類加載器和這個(gè)類本身一同確定其在 java 虛擬機(jī)中的唯一性。 ClassLoader.loadClass() 的代碼如下,先檢查是否已經(jīng)被加載過(guò),如果沒(méi)有則 parent.loadClass() 調(diào)用父加載器的 loadClass() 方法,如果父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器。如果父類加載器加載失敗,拋出 ClassNotFoundException,再調(diào)用自己的 findClass() 方法進(jìn)行加載。 另外,如果我們自己實(shí)現(xiàn)類加載器,一般是 Override 復(fù)寫 findClass 方法,而不是 loadClass 方法。 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); //可以O(shè)verride該方法 }} if (resolve) { resolveClass(c);} return c; }} JSP 與 Servlet 的關(guān)系
Servlet 生命周期主要是 java.servlet.Servlet 接口中的init() 、service() 、和 destroy() 3個(gè)方法。
GET 請(qǐng)求 vs POST 請(qǐng)求
HTTP 請(qǐng)求的基本格式
索引的分類主要分為聚集索引和非聚集索引:
ResultSet 統(tǒng)計(jì)記錄數(shù)目Java 中使用 JDBC 連接數(shù)據(jù)庫(kù),最后都會(huì)得到一個(gè) ResultSet,比如如下的代碼 Connection con = DriverManager.getConnection(url, username, password); Statement sta = con.createStatement(); String sql = 'select * from student'; ResultSet resultSet = sta.executeQuery(sql); 那么如何根據(jù)得到的 ResultSet 統(tǒng)計(jì)一共有多少條記錄呢?注意:ResultSet 沒(méi)有提供類似 size()、length 的 API 來(lái)直接獲取總記錄數(shù)。 方法1:利用循環(huán) while(resultSet.next()){ sum++; } 方法2:利用ResultSet的getRow方法來(lái)獲得ResultSet的總行數(shù) resultSet.last(); //移到最后一行 int rowCount = resultSet.getRow(); //得到當(dāng)前行號(hào),也就是記錄數(shù) 單例模式單例模式中必須保證只有一個(gè)實(shí)例存在。有時(shí)候單例是為了避免重復(fù)創(chuàng)建多個(gè)實(shí)例造成資源浪費(fèi),有時(shí)候也是為了避免多個(gè)不同的實(shí)例導(dǎo)致系統(tǒng)不一致的行為。 Android 中,App啟動(dòng)時(shí)系統(tǒng)會(huì)創(chuàng)建一個(gè) Application 對(duì)象,用來(lái)存儲(chǔ)系統(tǒng)的一些信息,這兒的 Application 就是是單例模式的應(yīng)用??梢酝ㄟ^(guò) Context.getApplicationContext() 獲取唯一的 Application 實(shí)例。 private volatile static Singleton instance; private Singleton() { } public static Singleton getInstance() { //第一重判斷 if (instance == null) { //鎖定代碼塊 synchronized (Singleton.class) { //第二重判斷 if (instance == null) { instance = new Singleton(); //創(chuàng)建單例實(shí)例 } } } return instance; } } 為什么 synchronized 里面需要加一次判斷 if (instance == null),是考慮這樣的特殊情形:比如線程A、B都到達(dá)第一個(gè) if (instance == null),線程A進(jìn)入 synchronized 代碼中創(chuàng)建實(shí)例,線程B排隊(duì)等待。但當(dāng)A執(zhí)行完畢時(shí),線程B進(jìn)入 synchronized 鎖定代碼,它并不知道實(shí)例已經(jīng)創(chuàng)建,將繼續(xù)創(chuàng)建新的實(shí)例,導(dǎo)致產(chǎn)生多個(gè)單例對(duì)象。 也可以用內(nèi)部類的方式創(chuàng)建, public class Singleton(){ private static class Inner { private static Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance(){ return Inner.instance; }} 模板方法模式在父類中實(shí)現(xiàn)一個(gè)算法不變的部分,并將可變的行為留給子類來(lái)實(shí)現(xiàn)。 比如 AsyncTask 里面的四個(gè)方法 onPreExecute、doInBackground、onProgressUpdate、onPostExecute 還有 Activity 也應(yīng)用了模板方法模式 適配器模式 分為兩種:類的適配器模式、對(duì)象的適配器模式 Android 里的 ListView 和 RecyclerView的 setAdapter() 方法就是使用了適配器模式。 觀察者模式在 GUI 中,不管是 Windows 桌面應(yīng)用、或者 Android、IOS,都會(huì)給某個(gè)按鈕 Button 設(shè)置監(jiān)聽事件,這兒就是使用了觀察者模式。Android 中設(shè)置 Button 的監(jiān)聽事件代碼如下: button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Perform action on click }}); 線程 VS 進(jìn)程關(guān)于線程和進(jìn)程,不正確的描述是__。(選 D 棧是線程私有, 保存其運(yùn)行狀態(tài)和局部變量 )
找出未打卡的員工 題目:輸入兩行數(shù)據(jù),第一行為全部員工的 id,第二行為某一天打卡的員工 id,已知只有一個(gè)員工沒(méi)有打卡,求出未打卡員工的 id。(員工 id 不重復(fù),每行輸入的 id 未排序) 輸入: 分析:可以用兩個(gè) List,第一個(gè) List 保存所有員工的 id,第二個(gè) List 保存打卡員工的 id,從第一個(gè)List 中把第二個(gè) List 的數(shù)據(jù)都刪除,最終剩下的就是未打卡員工的 id。 更好的方法:異或,兩行數(shù)據(jù)中未打卡員工的 id 出現(xiàn)了一次,其余員工的 id 都出現(xiàn)了2次,兩個(gè)相同的數(shù)異或?yàn)?。 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); } } 快速排序排序是經(jīng)典面試題,公司也希望通過(guò)手寫快排來(lái)考察面試者的編程習(xí)慣和基本功。 // 排序范圍 [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); }} // 一次劃分代碼,返回被劃分后的基準(zhǔn)位置 public static int partition(int[] arr, int left, int right) { int pivot = arr[left];while (left <>{ while (left < right="" &&="" arr[right]="">= pivot) right--; if (left <> arr[left++] = arr[right]; while (left < right="" &&="" arr[left]=""><=>=> left++; if (left <> arr[right--] = arr[left];} arr[left] = pivot; return left; }Note:快排是不穩(wěn)定的,常見的穩(wěn)定排序是:冒泡、插入、歸并 括號(hào)字符串是否合法某個(gè)字符串只包括 ( 和),判斷其中的括號(hào)是否匹配正確,比如 (()()) 正確,((())() 錯(cuò)誤,不允許使用棧。 這種類似題的常見思路是棧,對(duì)于左括號(hào)入棧,如果遇到右括號(hào),判斷此時(shí)棧頂是不是左括號(hào),是則將其出棧,不是則該括號(hào)序列不合法。 面試官要求不能使用棧,可以使用計(jì)數(shù)器,利用 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; }撲克牌隨機(jī)發(fā)牌對(duì)于52張牌,實(shí)現(xiàn)一個(gè)隨機(jī)打算撲克牌順序的程序。52張牌使用 int 數(shù)組模擬。 該算法的難點(diǎn)是如何保證隨機(jī)性?有個(gè)經(jīng)典算法 shuffle,思路就是遍歷數(shù)組,在剩下的元素里再隨機(jī)取一個(gè)元素,然后再在剩下的元素里再隨機(jī)取一個(gè)元素。每次取完元素后,我們就不會(huì)讓這個(gè)元素參與下一次的選取。 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 ≤ iexchange a[j] and a[i] 注意這兒是 0 ≤ j ≤ i,包括 j=i 的情況,因?yàn)榭赡芟磁坪竽硞€(gè)牌未發(fā)生交換,比如第51張牌還是原來(lái)的第51張牌。 public void randomCards() { int[] data = new int[52]; Random random= new Random(); for (int i = 0; i < data.length;=""> data[i] = i; for (int i = data.length - 1; i > 0; i--) { int temp = random.nextInt(i+1); //產(chǎn)生 [0,i] 之間的隨機(jī)數(shù) swap(data,i,temp);} } 金條付費(fèi)你讓工人為你工作7天,回報(bào)是一根金條,這個(gè)金條平分成相連的7段,你必須在每天結(jié)束的時(shí)候給他們一段金條,如果只允許你兩次把金條弄斷,你如何給你的工人付費(fèi)? 答案:切成一段,兩段,和四段。 第1天:給出1 賽馬25匹馬,速度都不同,但每匹馬的速度都是定值?,F(xiàn)在只有5條賽道,無(wú)法計(jì)時(shí),即每賽一場(chǎng)最多只能知道5匹馬的相對(duì)快慢。問(wèn)最少賽幾場(chǎng)可以找出25匹馬中速度最快的前3名? 答案:
本文作者:碼農(nóng)網(wǎng) – 陳小波
|
|