某公司(技術面)

一天沒顧得上吃飯,連續三面真的好累的,記錄一下面試過程中的問題吧,也算是對自己學習過程中的總結吧!

一面很奇怪HR面,二面技術面,三面是綜合面,在這裏主要說說二面過程中的問題吧!

和同學一起去的面試,大部分面了5分鐘結束,不知道面試官比較感興趣還是怎麼了,就給面了30多分鐘,其中的問題就記得下面這些,當然還有好多不記得了。

簡歷中寫了自己做過的兩個項目,開始是畫項目流程圖,還有爲什麼做這個項目,項目中遇到的最大問題是什麼,當時是怎麼解決的。

現在把一些基礎的問題記錄下來吧,雖然很基礎,但是每一個問題都有不同的側重點!
1. 什麼是多態,爲什麼要使用多態?
多態是同一個行爲具有多個不同表現形式或形態的能力。
多態性是對象多種表現形式的體現。
比如我們說"寵物"這個對象,它就有很多不同的表達或實現,比如有小貓、小狗、蜥蜴等等。那麼我到寵物店說"請給我一隻寵物",服務員給我小貓、小狗或者蜥蜴都可以,我們就說"寵物"這個對象就具備多態性。
接下來讓我們通過實例來了解Java的多態。
public interface Vegetarian{}
public class Animal{}
public class Deer extends Animal implements Vegetarian{}
實現多態的兩種方式:重載和覆蓋

  覆蓋,是指子類重新定義父類的虛函數的做法。

  重載,是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。

使用多態主要實現Java不能多繼承的問題。

 

2.面向對象的特性,那好你再說說Java語言的特性
面向對象的三個基本特徵是:封裝、繼承、多態。
Java語言的特性:面向對象、分佈式、解釋性、健壯、安全與系統無關、可移植、高性能、多線程和靜態的語言。

 

3.Java中單例模式有多少種,寫出其中的3個
懶漢式,線程不安全
public class Singleton {
    private static Singleton instance;
    private Singleton (){
    }
    public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
    }
}
這段代碼簡單明瞭,而且使用了懶加載模式,但是卻存在致命的問題。當有多個線程並行調用 getInstance() 的時候,就會創建多個實例。也就是說在多線程下不能正常工作。
懶漢式,線程安全爲了解決上面的問題,最簡單的方法是將整個 getInstance() 方法設爲同步(synchronized)。
public static synchronized Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}

雖然做到了線程安全,並且解決了多實例的問題,但是它並不高效。因爲在任何時候只能有一個線程調用 getInstance() 方法。但是同步操作只需要在第一次調用時才被需要,即第一次創建單例實例對象時。這就引出了雙重檢驗鎖。
雙重檢驗鎖雙重檢驗鎖模式(double checked locking pattern),是一種使用同步塊加鎖的方法。程序員稱其爲雙重檢查鎖,因爲會有兩次檢查 instance == null,一次是在同步塊外,一次是在同步塊內。爲什麼在同步塊內還要再檢驗一次?因爲可能會有多個線程一起進入同步塊外的 if,如果在同步塊內不進行二次檢驗的話就會生成多個實例了。
public static Singleton getSingleton() {
    if (instance == null) {                         //Single Checked
        synchronized (Singleton.class) {
            if (instance == null) {                 //Double Checked
                instance = new Singleton();
            }
        }
    }
    return instance ;
}

這段代碼看起來很完美,很可惜,它是有問題。主要在於instance = new Singleton()這句,這並非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情。

    1. 給 instance 分配內存
    2. 調用 Singleton 的構造函數來初始化成員變量
    3. 將instance對象指向分配的內存空間(執行完這步 instance 就爲非 null 了)

但是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序可能是 1-2-3 也可能是 1-3-2。如果是後者,則在 3 執行完畢、2 未執行之前,被線程二搶佔了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然後使用,然後順理成章地報錯。
我們只需要將 instance 變量聲明成 volatile 就可以了。
public class Singleton {
    private volatile static Singleton instance; //聲明成 volatile
    private Singleton (){}

    public static Singleton getSingleton() {
        if (instance == null) {                         
            synchronized (Singleton.class) {
                if (instance == null) {       
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
   
}

有些人認爲使用 volatile 的原因是可見性,也就是可以保證線程在本地不會存有 instance 的副本,每次都是去主內存中讀取。但其實是不對的。使用 volatile 的主要原因是其另一個特性:禁止指令重排序優化。也就是說,在 volatile 變量的賦值操作後面會有一個內存屏障(生成的彙編代碼上),讀操作不會被重排序到內存屏障之前。比如上面的例子,取操作必須在執行完 1-2-3 之後或者 1-3-2 之後,不存在執行到 1-3 然後取到值的情況。從「先行發生原則」的角度理解的話,就是對於一個 volatile 變量的寫操作都先行發生於後面對這個變量的讀操作(這裏的“後面”是時間上的先後順序)。
但是特別注意在 Java 5 以前的版本使用了 volatile 的雙檢鎖還是有問題的。其原因是 Java 5 以前的 JMM (Java 內存模型)是存在缺陷的,即時將變量聲明成 volatile 也不能完全避免重排序,主要是 volatile 變量前後的代碼仍然存在重排序問題。這個 volatile 屏蔽重排序的問題在 Java 5 中才得以修復,所以在這之後纔可以放心使用 volatile。
相信你不會喜歡這種複雜又隱含問題的方式,當然我們有更好的實現線程安全的單例模式的辦法。
餓漢式 static final field這種方法非常簡單,因爲單例的實例被聲明成 staticfinal 變量了,在第一次加載類到內存中時就會初始化,所以創建實例本身是線程安全的。
public class Singleton{
    //類加載時就初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}

    public static Singleton getInstance(){
        return instance;
    }
}

這種寫法如果完美的話,就沒必要在囉嗦那麼多雙檢鎖的問題了。缺點是它不是一種懶加載模式(lazy initialization),單例會在加載類後一開始就被初始化,即使客戶端沒有調用 getInstance()方法。餓漢式的創建方式在一些場景中將無法使用:譬如 Singleton 實例的創建是依賴參數或者配置文件的,在 getInstance() 之前必須調用某個方法設置參數給它,那樣這種單例寫法就無法使用了。
靜態內部類 static nested class我比較傾向於使用靜態內部類的方法,這種方法也是《Effective Java》上所推薦的。
public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

這種寫法仍然使用JVM本身機制保證了線程安全問題;由於 SingletonHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它,因此它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷;也不依賴 JDK 版本。
枚舉 Enum用枚舉寫單例實在太簡單了!這也是它最大的優點。下面這段代碼就是聲明枚舉實例的通常做法。
public enum EasySingleton{
    INSTANCE;
}

我們可以通過EasySingleton.INSTANCE來訪問實例,這比調用getInstance()方法簡單多了。創建枚舉默認就是線程安全的,所以不需要擔心double checked locking,而且還能防止反序列化導致重新創建新的對象。但是還是很少看到有人這樣寫,可能是因爲不太熟悉吧。
總結一般來說,單例模式有五種寫法:懶漢、餓漢、雙重檢驗鎖、靜態內部類、枚舉。上述所說都是線程安全的實現,文章開頭給出的第一種方法不算正確的寫法。
就我個人而言,一般情況下直接使用餓漢式就好了,如果明確要求要懶加載(lazy initialization)會傾向於使用靜態內部類,如果涉及到反序列化創建對象時會試着使用枚舉的方式來實現單例。
4.java final finally finalize有什麼不同
(1) final:修飾符(關鍵字),如果一個類被聲明爲final,意味着它不能再派生出新的子類,不能作爲父類被繼承。因此一個類不能既被聲明爲 abstract的,又被聲明爲final的。將變量或方法聲明爲final,可以保證它們在使用中不被改變。被聲明爲final的變量必須在聲明時給定初值,而在以後的引用中只能讀取,不可修改。被聲明爲final的方法也同樣只能使用,不能重載

(2) finally:在異常處理時提供 finally 塊來執行任何清除操作。如果拋出一個異常,那麼相匹配的 catch 子句就會執行,然後控制就會進入 finally塊(如果有的話)。

(3) finalize:方法名。Java 技術允許使用 finalize() 方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在確定這個對象沒有被引用時對這個對象調用的。它是在 Object 類中定義的,因此所有的類都繼承了它。子類覆蓋 finalize() 方法以整理系統資源或者執行其他清理工作。finalize() 方法是在垃圾收集器刪除對象之前對這個對象調用的。

 

 
5.有沒有使用過接口,具體說說接口有什麼好處
接口的概念其實並不難理解, 接口關鍵字Interface, 在使用時可以只定義函數體而不需求詳細的實現。 再類的繼承過程中可以實現多個接口而取代了類的多繼承。

 

6.(接上一個問題)抽象類使用過吧?談談接口和抽象類的區別
在使用抽象類時需要注意幾點:
         1、抽象類不能被實例化,實例化的工作應該交由它的子類來完成,它只需要有一個引用即可。
         2、抽象方法必須由子類來進行重寫。
         3、只要包含一個抽象方法的抽象類,該方法必須要定義成抽象類,不管是否還包含有其他方法。
         4、抽象類中可以包含具體的方法,當然也可以不包含抽象方法。
         5、子類中的抽象方法不能與父類的抽象方法同名。
         6、abstract不能與final並列修飾同一個類。
         7、abstract 不能與private、static、final或native並列修飾同一個方法。
抽象類--抽象類是用來捕捉子類的通用特性的 。它不能被實例化,只能被用作子類的超類。抽象類是被用來創建繼承層級裏子類的模板

接口-- -接口是抽象方法的集合。如果一個類實現了某個接口,那麼它就繼承了這個接口的抽象方法。這就像契約模式,如果實現了這個接口,那麼就必須確保使用這些方法。接口只是一種形式,接口自身不能做任何事情。
 

什麼時候使用抽象類和接口

如果你擁有一些方法並且想讓它們中的一些有默認實現,那麼使用抽象類吧。
如果你想實現多重繼承,那麼你必須使用接口。由於Java不支持多繼承,子類不能夠繼承多個類,但可以實現多個接口。因此你就可以使用接口來解決它。
如果基本功能在不斷改變,那麼就需要使用抽象類。如果不斷改變基本功能並且使用接口,那麼就需要改變所有實現了該接口的類。

 

7.String類瞭解吧?看過源代碼嗎,講一講String函數的定義,String能被繼承嗎?
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[]; //用來存儲字符串轉換而來的字符數組

    /** The offset is the first index of the storage that is used. */
    private final int offset; //字符串起始字符在字符數組的位置

    /** The count is the number of characters in the String. */
    private final int count; //字符串分解成字符數組後字符的數目
}

 

8.數據庫學的怎麼樣,增刪改查應該知道吧。
 1 增:  
 2 insert into 表名 values(0,'測試');  
 3 注:如上語句,表結構中有自動增長的列,也必須爲其指定一個值,通常爲0  
 4 insert into 表名(id,name) values(0,'尹當')--同上  
 5 2.刪數據:  
 6 delete from 表名;  
 7 delete from 表名 where id=1;  
 8 刪除結構:  
 9 刪數據庫:drop database 數據庫名;  
10 刪除表:drop table 表名;  
11 刪除表中的列:alter table 表名 drop column 列名;  
12 3. 改:  
13 修改所有:updata 表名 set 列名='新的值,非數字加單引號' ;  
14 帶條件的修改:updata 表名 set 列名='新的值,非數字加單引號' where id=6;  
15 4.查:  
16 查詢所有的數據:select *from 表名;  
17 帶條件的查詢:  
18 select *from 表名 where 列名=條件值;  
19 Select * from 表名 where 列名 not like(like) '字符值'  
20 分頁查詢:select *from 表名 limit 每頁數量 offset 偏移量; 

 

9.說說order by 和group by 的區別
order    by     是按字段排序    group    by     是按字段分類 

 

 
10.看你簡歷熟悉linux命令,說說你常使用的10個命令,ls 命令都有哪些參數、
1.ls – List
ls會列舉出當前工作目錄的內容(文件或文件夾),就跟你在GUI中打開一個文件夾去看裏面的內容一樣。
2.mkdir – Make Directory mkdir <new-directory-name>常見一個新目錄
3.pwd – Print Working Directory pwd顯示當前工作目錄 4.cd – Change Directory 對於當前在終端運行的會中中,cd <directory>將給定的文件夾(或目錄)設置成當前工作目錄。 5.rmdir – Remove Directory rmdir <directory-name>刪除給定的目錄。 6.rm – Remove rm <file-name>會刪除給定的文件或文件夾,可以使用rm -r <directory-name>遞歸刪除文件夾 7.cp – Copy cp <source-file> <destination-file>命令對文件或文件夾進行復制,可以使用cp -r <source-folder> <destination-folder> 選項來遞歸複製文件夾。 8.mv – MoVe mv <source> <destination>命令對文件或文件夾進行移動,如果文件或文件夾存在於當前工作目錄,還可以對文件或文件夾進行重命名。 9.cat – concatenate and print files cat <file>用於在標準輸出(監控器或屏幕)上查看文件內容。 10.tail – print TAIL (from last) > tail <file-name>默認在標準輸出上顯示給定文件的最後10行內容,可以使用tail -n N <file-name>指定在標準輸出上顯示文件的最後N行內容。 11.less – print LESS less <file-name>按頁或按窗口打印文件內容。在查看包含大量文本數據的大文件時是非常有用和高效的。你可以使用Ctrl+F向前翻頁,Ctrl+B向後翻頁。 12.grep grep "<string>" <file-name>在給定的文件中搜尋指定的字符串。grep -i "<string>" <file-name>在搜尋時會忽略字符串的大小寫,而grep -r "<string>" <file-name>則會在當前工作目錄的文件中遞歸搜尋指定的字符串。 13.Find 這個命令會在給定位置搜尋與條件匹配的文件。你可以使用find <folder-to-search> -name <file-name>的-name選項來進行區分大小寫的搜尋,find <folder-to-search> -iname <file-name>來進行不區分大小寫的搜尋。 find <folder-to-search> -iname <file-name> 14.tar tar命令能創建、查看和提取tar壓縮文件。tar -cvf <archive-name.tar> <file1-OR-file2-OR-both-to-archive>是創建對應壓縮文件,tar -tvf <archive-to-view.tar>來查看對應壓縮文件,tar -xvf <archive-to-extract.tar>來提取對應壓縮文件。 15.gzip gzip <filename>命令創建和提取gzip壓縮文件,還可以用gzip -d <filename>來提取壓縮文件。 16.unzip unzip <archive-to-extract.zip>對gzip文檔進行解壓。在解壓之前,可以使用unzip -l <archive-to-extract.zip>命令查看文件內容。 17.help <command-name> --help會在終端列出所有可用的命令,可以使用任何命令的-h或-help選項來查看該命令的具體用法。 18.whatis – What is this command whatis <command-name>會用單行來描述給定的命令。 19.man – Manual man <command-name>會爲給定的命令顯示一個手冊頁面。 20.exit exit用於結束當前的終端會話。 21.ping ping <remote-host-address>通過發送數據包ping遠程主機(服務器),常用與檢測網絡連接和服務器狀態。 22.who – Who Is logged in who能列出當前登錄的用戶名。 23.su – Switch User su <username>用於切換不同的用戶。即使沒有使用密碼,超級用戶也能切換到其它用戶。 24.uname uname會顯示出關於系統的重要信息,如內核名稱、主機名、內核版本、處理機類型等等,使用uname -a可以查看所有信息。 25.free – Free memory free會顯示出系統的空閒內存、已經佔用內存、可利用的交換內存等信息,free -m將結果中的單位轉換成KB,而free –g則轉換成GB。 26.df – Disk space Free df查看文件系統中磁盤的使用情況–硬盤已用和可用的存儲空間以及其它存儲設備。你可以使用df -h將結果以人類可讀的方式顯示。 27.ps – ProcesseS ps顯示系統的運行進程。 28.Top – TOP processes top命令會默認按照CPU的佔用情況,顯示佔用量較大的進程,可以使用top -u <username>查看某個用戶的CPU使用排名情況。 29.shutdown shutdown用於關閉計算機,而shutdown -r用於重啓計算機。

 

11.linux 文件的權限瞭解吧,講講,後面給出個例子問相關的權限
12.多線程怎麼實現的,寫一個多線程吧
13.框架了解吧,說說Spring的生命週期
14.你使用過Jsp,那好你說說JSP 是什麼?Jsp的生命週期又是什麼。JSP有哪些指令。
◆裝載和實例化:服務端爲JSP頁面查找已有的實現類,如果沒找到則創建新的JSP頁面的實現類,然後把這個類載入JVM。在實現類裝載完成之後,JVM將創建這個類的一個實例。這一步會在裝載後立刻執行,或者在第一次請求時執行。
◆初始化:初始化JSP頁面對象。如果你希望在初始化期間執行某些代碼,那麼你可以向頁面中增加一個初始化方法(method),在初始化的時候就會調用該方法。
◆請求處理:由頁面對象響應客戶端的請求。需要注意的是,單個對象實例將處理所有的請求。在執行完處理之後,服務器將一個響應(response)返回給客戶端。這個響應完全是由HTML標籤和其他數據構成的,並不會把任何Java源碼返回給客戶端。
◆生命週期終止:服務器不再把客戶端的請求發給JSP。在所有的請求處理完成之後,會釋放掉這個類的所有實例。一般這種情況會發生在服務器關閉的時候,但是也有其他的可能性,比如服務器需要保存資源、檢測到有JSP文件更新,或者由於其他某些原因需要終止實例等情況。如果想讓代碼執行清除工作,那麼可以實現一個方法,並且在這個類實例釋放之前調用該方法。本章隨後一節“處理JSP的初始化和終止”將對此加以討論。
 
6.Java中爲什麼不能使用多繼承?
15.寫一個冒泡排序吧
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章