面試(基礎篇)

1、基礎部分

1. Java 中 i++ 操做符是線程安全的嗎?html

不是線程安全的操做。它涉及到多個指令,如讀取變量值增長,而後存儲回內存,這個過程可能會出現多個線程交差。java

2. a = a + b 與 a += b 的區別面試

+= 隱式的將加操做的結果類型強制轉換爲持有結果的類型。若是兩這個整型相加,如 byte、short 或者 int,首先會將它們提高到 int 類型,而後在執行加法操做。 a+b 會出現編譯錯誤,可是 b += a 沒問題,但是 b += a 會存在丟失位數 ,以下:算法

byte a = 127;
byte b = 127;
// b = a + b; 編譯出錯,須要的是byte找到的爲int
b += a; // 結果爲-2

3. 3*0.1 == 0.3 將會返回什麼?true 仍是 false?數據庫

false,由於有些浮點數不能徹底精確的表示出來。編程

4. 爲何 Java 中的 String 是不可變的(Immutable)?設計模式

緣由:①字符串池;②安全;③在類加載機制中使用大量的字符串;④多線程之間共享;⑤優化和性能。缺點因爲 String 是不可變的,所以會產生大量的臨時對象,這就爲垃圾收集器創造了壓力。Java 設計人員已經考慮過了,使用字符串池中是他們減小字符串垃圾的解決方案。字符串池位於 JVM 的方法區(PermGen 空間)中,與 JVM 堆相比,它很是有限,當字符串過多時會致使 OutOfMemoryError。從 Java7 開始,已經將字符串池搬遷至堆中。資料緩存

5. Java 中的編譯期常量是什麼?使用它又什麼風險?  安全

編譯期常量就是所謂的 public final static 常量。 因爲在編譯時就肯定了值,在使用的場合會直接寫成值。而不是直接到原來的類中讀取。這樣會有一個問題。 若是類 A 提供了常量  類 B 使用了常量。並都進行了編譯。而後,又修改了類 A 的源碼,調用系統進行編譯。系統發現類 A 是新的代碼,編譯了,類 B 仍然是舊的代碼,就不進行編譯,使用舊的類。因此致使類 A 的修改沒法反映到類 B 中。這樣形成了讀取變量的值不一樣的風險服務器

6. String、StringBuilder、StringBuffer 深刻理解,各自的使用場景。

面試題 – 請說出下面程序的輸出。

String s1 = "Programming";
String s2 = new String("Programming");
String s3 = "Program" + "ming";
System.out.println(s1 == s2); //false
System.out.println(s1 == s3); //true
System.out.println(s1 == s1.intern()); //true

補充:String 對象的 intern 方法會獲得字符串對象在常量池中對應的版本的引用(若是常量池中有一個字符串與 String 對象的 equals 結果是 true),若是常量池中沒有對應的字符串,則該字符串將被添加到常量池中,而後返回常量池中字符串的引用。

7. 抽象類(abstract class)和接口(interface)有什麼異同?

8. 靜態嵌套類(Static Nested Class)和內部類(Inner Class)的不一樣?

Static Nested Class 是被聲明爲靜態(static)的內部類,它能夠不依賴於外部類實例被實例化。而一般的內部類須要在外部類實例化後才能實例化,其語法看起來挺詭異的,以下所示。

/**
 * 撲克類(一副撲克)
 * @author 駱昊
 *
 */
public class Poker {
    private static String[] suites = {"黑桃", "紅桃", "草花", "方塊"};
    private static int[] faces = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
 
    private Card[] cards;
 
    /**
     * 構造器
     * 
     */
    public Poker() {
        cards = new Card[52];
        for(int i = 0; i < suites.length; i++) {
            for(int j = 0; j < faces.length; j++) {
                cards[i * 13 + j] = new Card(suites[i], faces[j]);
            }
        }
    }
 
    /**
     * 洗牌 (隨機亂序)
     * 
     */
    public void shuffle() {
        for(int i = 0, len = cards.length; i < len; i++) {
            int index = (int) (Math.random() * len);
            Card temp = cards[index];
            cards[index] = cards[i];
            cards[i] = temp;
        }
    }
 
    /**
     * 發牌
     * @param index 發牌的位置
     * 
     */
    public Card deal(int index) {
        return cards[index];
    }
 
    /**
     * 卡片類(一張撲克)
     * [內部類]
     * @author 駱昊
     *
     */
    public class Card {
        private String suite;   // 花色
        private int face;       // 點數
 
        public Card(String suite, int face) {
            this.suite = suite;
            this.face = face;
        }
 
        @Override
        public String toString() {
            String faceStr = "";
            switch(face) {
            case 1: faceStr = "A"; break;
            case 11: faceStr = "J"; break;
            case 12: faceStr = "Q"; break;
            case 13: faceStr = "K"; break;
            default: faceStr = String.valueOf(face);
            }
            return suite + faceStr;
        }
    }
}
View Code

測試代碼:

class PokerTest {
 
    public static void main(String[] args) {
        Poker poker = new Poker();
        poker.shuffle();                // 洗牌
        Poker.Card c1 = poker.deal(0);  // 發第一張牌
        // 對於非靜態內部類Card
        // 只有經過其外部類Poker對象才能建立Card對象
        Poker.Card c2 = poker.new Card("紅心", 1);    // 本身建立一張牌
 
        System.out.println(c1);     // 洗牌後的第一張
        System.out.println(c2);     // 打印: 紅心A
    }
}
View Code

面試題 – 下面的代碼哪些地方會產生編譯錯誤?

class Outer {
    class Inner {}
    public static void foo() { new Inner(); }
    public void bar() { new Inner(); }
    public static void main(String[] args) {
        new Inner();
    }
}

注意:Java 中非靜態內部類對象的建立要依賴其外部類對象,上面的面試題中 foo 和 main 方法都是靜態方法,靜態方法中沒有 this,也就是說沒有所謂的外部類對象,所以沒法建立內部類對象,若是要在靜態方法中建立內部類對象,能夠這樣作:

new Outer().new Inner();

9. Java 中會存在內存泄漏嗎,請簡單描述。

理論上 Java 由於有垃圾回收機制(GC)不會存在內存泄露問題(這也是 Java 被普遍使用於服務器端編程的一個重要緣由);然而在實際開發中,可能會存在無用但可達的對象,這些對象不能被 GC 回收,所以也會致使內存泄露的發生。例如 Hibernate 的 Session(一級緩存)中的對象屬於持久態,垃圾回收器是不會回收這些對象的,然而這些對象中可能存在無用的垃圾對象,若是不及時關閉(close)或清空(flush)一級緩存就可能致使內存泄露。下面例子中的代碼也會致使內存泄露。

import java.util.Arrays;
import java.util.EmptyStackException;
 
public class MyStack<T> {
    private T[] elements;
    private int size = 0;
 
    private static final int INIT_CAPACITY = 16;
 
    public MyStack() {
        elements = (T[]) new Object[INIT_CAPACITY];
    }
 
    public void push(T elem) {
        ensureCapacity();
        elements[size++] = elem;
    }
 
    public T pop() {
        if(size == 0) 
            throw new EmptyStackException();
        return elements[--size];
    }
 
    private void ensureCapacity() {
        if(elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}
View Code

上面的代碼實現了一個棧(先進後出 FILO)結構,乍看之下彷佛沒有什麼明顯的問題,它甚至能夠經過你編寫的各類單元測試。然而其中的 pop 方法卻存在內存泄露的問題,當咱們用 pop 方法彈出棧中的對象時,該對象不會被看成垃圾回收,即便使用棧的程序再也不引用這些對象,由於棧內部維護着對這些對象的過時引用(obsolete reference)。在支持垃圾回收的語言中,內存泄露是很隱蔽的,這種內存泄露其實就是無心識的對象保持。若是一個對象引用被無心識的保留起來了,那麼垃圾回收器不會處理這個對象,也不會處理該對象引用的其餘對象,即便這樣的對象只有少數幾個,也可能會致使不少的對象被排除在垃圾回收以外,從而對性能形成重大影響,極端狀況下會引起 Disk Paging(物理內存與硬盤的虛擬內存交換數據),甚至形成 OutOfMemoryError。

10. 抽象的(abstract)方法是否可同時是靜態的(static),是否可同時是本地方法(native),是否可同時被synchronized修飾?

都不能。抽象方法須要子類重寫,而靜態的方法是沒法被重寫的,所以兩者是矛盾的。本地方法是由本地代碼(如C代碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。synchronized和方法的實現細節有關,抽象方法不涉及實現細節,所以也是相互矛盾的。

11. 深度克隆。

12. 得到一個類的類對象有哪些方式?

①類型.class;②對象.getClass();③Class.forName("對象名")。

13. 如何經過反射建立對象?

①經過類對象調用 newInstance() 方法,例如:String.class.newInstance();

②經過類對象的 getConstructor() 或 getDeclaredConstructor() 方法得到構造器(Constructor)對象並調用其 newInstance() 方法建立對象,例如:String.class.getConstructor(String.class).newInstance(「Hello」);

14. HTTP請求的GET與POST方式的區別

15. Session與Cookie區別

2、集合

1. HashMap 的原理。圖解 HashMap 的內部工做機制

面試題 – 爲何恰當地設置 HashMap 的初始容量(initial capacity)是最佳實踐?能夠減小重散列的發生。

2. 多線程狀況下 HashMap 死循環的問題。

3. ConcurrentHashMap 的工做原理及代碼實現。 

4. HashSet 實現原理。深刻 Java 集合學習系列:HashSet 的實現原理

HashSet 中的元素,只是存放在了底層 HashMap 的 key 上,value使用一個static final的Object對象標識。

5. 保證插入順序的 HashMap。保證插入順序的HashMap--LinkedHashMap的存取原理

6. TreeMap、TreeSet 的排序時如何實現比較元素的?

7. 有沒有可能兩個不相等的對象有有相同的 hashcode?

8. 兩個相同的對象會有不一樣的的 hashcode 嗎?

9. Comparator 與 Comparable 有什麼不一樣?

10. Collection 和 Collections 的區別?

11. 爲何在重寫 equals 方法的時候須要重寫 hashCode 方法?

12. Fast-Fail 機制。

推薦莫等閒深刻 Java 集合學習系列

3、多線程與併發編程

1. 用 wait-notify 寫一段代碼來解決生產者-消費者問題?

2. 用 Java 寫一個線程安全的單例模式(Singleton)?

3. Java 中 sleep 方法和 wait 方法的區別?

4. 如今有 T一、T二、T3 三個線程,你怎樣保證 T2 在 T1 執行完後執行,T3 在 T2 執行完後執行。參考資料

思路1:在 T1 線程啓動以後,thread1.join(),同理在 T2 線程啓動以後,thread2.join(),同理在 T2 線程啓動以後,thread3.join()。

public class TestJoin {
    public static void main(String[] args) {
        Thread t1 = new MyThread("線程1");
        Thread t2 = new MyThread("線程2");
        Thread t3 = new MyThread("線程3");

        try {
            //t1先啓動
            t1.start();
            t1.join();
            //t2
            t2.start();
            t2.join();
            //t3
            t3.start();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }
}

class MyThread extends Thread {
    public MyThread(String name) {
        setName(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
View Code

思路2:在 T2 線程執行前調用 thread1.join(),在 T3 線程執行前調用 thread2.join()。

public class TestJoin2 {
    public static void main(String[] args) {
        final Thread t1 = new Thread(() -> System.out.println("t1"));
        final Thread t2 = new Thread(() -> {
            try {
                //引用t1線程,等待t1線程執行完
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2");
        });
        Thread t3 = new Thread(() -> {
            try {
                //引用t2線程,等待t2線程執行完
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t3");
        });
        t3.start();
        t2.start();
        t1.start();
    }
}
View Code

5. 說說 CountDownLatch、CyclicBarrier 原理和區別 。何時使用 CountDownLatch循環柵欄:CyclicBarrier(司令要求任務) 讀書筆記

6. 說說 Semaphore 原理。

7. 說說 Exchanger 原理。

8. ThreadLocal 原理分析,ThreadLocal 爲何會出現 OOM,出現的深層次原理。

9. 講講線程池的實現原理。

10. 線程池的幾種實現方式。

11. 線程的生命週期,狀態是如何轉移的。

12. 產生死鎖的四個條件。

互斥、請求與保持、不剝奪、循環等待

13. 如何檢查死鎖。

經過jConsole檢查死鎖

14. volatile 實現原理。

禁止指令重排、刷新內存

15. synchronized 實現原理。

對象監視器

16. synchronized 與 lock 的區別。

17. AQS 同步隊列。

18. 什麼是 CAS。Java併發編程之CAS

CAS(Compare and swap)比較和替換是設計併發算法時用到的一種技術。簡單來講,比較和替換是使用一個指望值和一個變量的當前值進行比較,若是當前變量的值與咱們指望的值相等,就使用一個新值替換當前變量的值。

19. 什麼是 ABA 問題,JDK 是如何解決的?

由於 CAS 須要在操做值的時候檢查下值有沒有發生變化,若是沒有發生變化則更新,可是若是一個值原來是 A,變成了 B,又變成了 A,那麼使用 CAS 進行檢查時會發現它的值沒有發生變化,可是實際上卻變化了。ABA 問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麼 A-B-A 就會變成 1A-2B-3A。從 Java1.5 開始 JDK 的 atomic 包裏提供了一個類 AtomicStampedReference 來解決 ABA 問題。這個類的 compareAndSet 方法做用是首先檢查當前引用是否等於預期引用,而且當前標誌是否等於預期標誌,若是所有相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。

20. 偏向鎖、輕量級鎖、重量級鎖、自旋鎖的概念。

推薦:《Java 併發編程的藝術》、《Java 多線程編程核心技術》

4、JVM

1. JVM 運行時內存區域劃分

2. 內存溢出 OOM 和堆棧溢出 SOE 的示例及緣由、如何排查與解決。

3. 如何判斷對象是否能夠回收或存活。

4. 常見的 GC 回收算法及其含義。

5. 常見的 JVM 性能監控和故障處理工具類:jps、jstat、jmap、jinfo、jconsole 等。

6. JVM 如何設置參數。

7. JVM性能調優。

8. 類加載器、雙親委派模型、一個類的生命週期、類是如何加載到 JVM 中的。

9. 類加載的過程:加載、驗證、準備、解析、初始化。

10.  強引用,軟引用,弱引用和虛引用

11. Java 內存模型 JMM。

5、設計模式

6、網絡 I/O 基礎

7、JDBC

1. SQL 注入

2. 闡述 JDBC 操做數據庫的步驟。

下面的代碼以鏈接本機的 Oracle 數據庫爲例,演示 JDBC 操做數據庫的步驟。

// 1.加載驅動
Class.forName("oracle.jdbc.driver.OracleDriver");
// 2.建立鏈接
Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "tiger"); // 3.建立語句 PreparedStatement ps = con.prepareStatement("select * from emp where sal between ? and ?"); ps.setInt(1, 1000); ps.setInt(2, 3000); // 4.執行語句 ResultSet rs = ps.executeQuery(); // 5.處理結果 while(rs.next()) { System.out.println(rs.getInt("empno") + " - " + rs.getString("ename")); } // 6.關閉資源 finally { if(con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } }

關閉外部資源的順序應該和打開的順序相反,也就是說先關閉 ResultSet、再關閉 Statement、在關閉 Connection。上面的代碼只關閉了 Connection(鏈接),雖然一般狀況下在關閉鏈接時,鏈接上建立的語句和打開的遊標也會關閉,但不能保證老是如此,所以應該按照剛纔說的順序分別關閉。此外,第一步加載驅動在 JDBC 4.0 中是能夠省略的(自動從類路徑中加載驅動),可是咱們建議保留。

3. Statemen t和 PreparedStatement 有什麼區別?哪一個性能更好?

與 Statement 相比,①PreparedStatement 接口表明預編譯的語句,它主要的優點在於能夠減小 SQL 的編譯錯誤並增長 SQL 的安全性(減小 SQL 注射攻擊的可能性);②PreparedStatement 中的 SQL 語句是能夠帶參數的,避免了用字符串鏈接拼接 SQL 語句的麻煩和不安全;③當批量處理 SQL 或頻繁執行相同的查詢時,PreparedStatement 有明顯的性能上的優點,因爲數據庫能夠將編譯優化後的 SQL 語句緩存起來,下次執行相同結構的語句時就會很快(不用再次編譯和生成執行計劃)。

補充:爲了提供對存儲過程的調用,JDBC API 中還提供了 CallableStatement 接口。存儲過程(Stored Procedure)是數據庫中一組爲了完成特定功能的 SQL 語句的集合,經編譯後存儲在數據庫中,用戶經過指定存儲過程的名字並給出參數(若是該存儲過程帶有參數)來執行它。雖然調用存儲過程會在網絡開銷、安全性、性能上得到不少好處,可是存在若是底層數據庫發生遷移時就會有不少麻煩,由於每種數據庫的存儲過程在書寫上存在很多的差異。

4. 使用JDBC操做數據庫時,如何提高讀取數據的性能?如何提高更新數據的性能?

要提高讀取數據的性能,能夠指定經過結果集(ResultSet)對象的 setFetchSize() 方法指定每次抓取的記錄數(典型的空間換時間策略);要提高更新數據的性能可使用 PreparedStatement 語句構建批處理,將若干SQL語句置於一個批處理中執行。

5. 在進行數據庫編程時,鏈接池有什麼做用?

因爲建立鏈接和釋放鏈接都有很大的開銷(尤爲是數據庫服務器不在本地時,每次創建鏈接都須要進行 TCP 的三次握手,釋放鏈接須要進行 TCP 四次握手,形成的開銷是不可忽視的),爲了提高系統訪問數據庫的性能,能夠事先建立若干鏈接置於鏈接池中,須要時直接從鏈接池獲取,使用結束時歸還鏈接池而沒必要關閉鏈接,從而避免頻繁建立和釋放鏈接所形成的開銷,這是典型的用空間換取時間的策略(浪費了空間存儲鏈接,但節省了建立和釋放鏈接的時間)。池化技術在 Java 開發中是很常見的,在使用線程時建立線程池的道理與此相同。基於 Java 的開源數據庫鏈接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid 等。

相關文章
相關標籤/搜索