Java面試題沖刺第一天–基礎篇1

面試題1:Java 中操作字符串都有哪些類?它們之間有什麼區別? 正經回答:

操作字符串的類有:StringStringBufferStringBuilder

String 和 StringBuffer、StringBuilder 的區別在於 String 聲明的是不可變的對象,每次操作都會生成新的 String 對象,然後將指針指向新的 String 對象,而 StringBuffer、StringBuilder 可以在原有對象的基礎上進行操作,所以在經常改變字符串內容的情況下最好不要使用 String。

而StringBuffer 和 StringBuilder 最大的區別在於,StringBuffer 是線程安全的,而 StringBuilder 是非線程安全的,但 StringBuilder 的性能卻高於 StringBuffer,所以在單線程環境下推薦使用 StringBuilder,多線程環境下推薦使用 StringBuffer。

String StringBuffer StringBuilder
類是否可變 不可變(Final) 可變 可變
功能介紹 每次對String的操作都會在“常量池”中生成新的String對象 任何對它指向的字符串的操作都不會產生新的對象。每個StringBuffer對象都有一定的緩沖區容量,字符串大小沒有超過容量時,不會分配新的容量,當字符串大小超過容量時,自動擴容 功能與StringBuffer相同,相比少瞭同步鎖,執行速度更快
線程安全性 線程安全 線程安全 線程不安全
使用場景推薦 單次操作或循環外操作字符串 多線程操作字符串 單線程操作字符串

深入追問:

追問1:這三者在效率上怎麼說?

StringBulider > StringBuffer > String

String <(StringBuffer,StringBuilder)的原因?

  • String:字符串常量
  • StringBuffer:字符串變量(有同步鎖)
  • StringBuilder:字符串變量(無同步鎖)

從上面的名字可以看到,String是”字符串常量”,也就是不可改變的對象。源碼如下:

public final class String{}

對於上面這句話的理解你可能會產生這樣一個疑問 ,比如這段代碼:

String str = "唐伯虎";
str = str + "點香煙";
System.out.print(str); // result : "唐伯虎點香煙"

我們明明改變瞭String型的變量str啊,為什麼說是沒有改變呢?我們來看一下這張對String操作時內存變化的圖:

在這裡插入圖片描述

我們可以看到,初始String值為”唐伯虎”,然後在這個字符串後面加上新的字符串”點香煙”,這個過程是需要重新在棧堆內存中開辟內存空間的,最終得到瞭”唐伯虎點香煙”字符串也相應的需要開辟內存空間,這樣短短的兩個字符串,卻需要開辟三次內存空間,不得不說這是對內存空間的極大浪費,執行效率同理。

為瞭應對經常性操作字符串的場景,Java才提供瞭其他兩個操作字符串的類 —— StringBuffer、StringBuilder。

他們倆均屬於字符串變量,是可改變的對象,每當我們用它們對字符串做操作時,實際上是在一個對象上操作的,這樣就不會像String一樣創建一些而外的對象進行操作瞭,速度自然就相對快瞭。

我們一般在StringBuffer、StringBuild類上的主要操作是 append 和 insert 方法,這些方法允許被重載,以接受任意類型的數據。每個方法都能有效地將給定的數據轉換成字符串,然後將該字符串的字符追加或插入到字符串緩沖區中。append 方法始終將這些字符添加到緩沖區的末端;而 insert 方法則在指定的點(index)添加字符。

  1. StringBuilder一個可變的字符序列是JDK1.5新增的。此類提供一個與 StringBuffer 兼容的 API,但不保證同步。該類被設計用作 StringBuffer 的一個簡易替換,用在字符串緩沖區被單個線程使用的時候(這種情況很普遍)。如果可能,建議優先采用S
  2. tringBuilder類,因為在大多數實現中,它比 StringBuffer 要快。且兩者的方法基本相同。然而在應用程序要求線程安全的情況下,則必須使用 StringBuffer 類。

String 類型和 StringBuffer、 StringBuild類型的主要性能區別其實在於 String 是不可變的對象(final), 因此在每次對 String 類型進行改變的時候其實都等同於在堆中生成瞭一個新的 String 對象,然後將指針指向新的 String 對象,這樣不僅效率低下,而且大量浪費有限的內存空間,所以經常改變內容的字符串最好不要用 String 。因為每次生成對象都會對系統性能產生影響,特別是當內存中的無引用對象過多瞭以後, JVM 的 GC 開始工作,那速度是一定會相當慢的。另外當GC清理速度跟不上new String的速度時,還會導致內存溢出Error,會直接kill掉主程序!報錯如下:

Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded

Exception in thread “I/O dispatcher 3797236” java.lang.OutOfMemoryError: GC overhead limit exceeded

追問2:那StringBuffer和StringBuffer線程安全主要差在哪裡呢?

StringBuffer和StringBuilder可以算是雙胞胎瞭,這兩者的方法沒有很大區別。但在線程安全性方面,StringBuffer允許多線程進行字符操作。這是因為在源代碼中StringBuffer的很多方法都被關鍵字synchronized 修飾瞭,而StringBuilder沒有。

synchronized的含義:   

每一個類對象都對應一把鎖,當某個線程A調用類對象O中的synchronized方法M時,必須獲得對象O的鎖才能夠執行M方法,否則線程A阻塞。一旦線程A開始執行M方法,將獨占對象O的鎖。使得其它需要調用O對象的M方法的線程阻塞。隻有線程A執行完畢,釋放鎖後。那些阻塞線程才有機會重新調用M方法。這就是解決線程同步問題的鎖機制。 >  瞭解瞭synchronized的含義以後,大傢可能都會有這個感覺。多線程編程中StringBuffer比StringBuilder要安全多瞭 ,事實確實如此。如果有多個線程需要對同一個字符串緩沖區進行操作的時候,StringBuffer應該是不二選擇。

註意:是不是String也不安全呢?事實上不存在這個問題,String是不可變的。線程對於堆中指定的一個String對象隻能讀取,無法修改。試問:還有什麼不安全的呢?

實際應用場景中:

  • 如果不是在循環體中進行字符串拼接的話,直接使用 String 的 “+” 就好瞭;
  • 單線程循環中操作大量字符串數據 → StringBuilder.append();
  • 多線程循環中操作大量字符串數據 → StringBuffer.append();

面試題2:請你說一下Error 和 Exception 區別是什麼?

正經回答:

Error 和 Exception 都是 Throwable 的子類,在Java中隻有Throwable類型的實例才可以被拋出或者捕獲,它是異常處理機制的基本類型。

在這裡插入圖片描述

  • Exception和Error體現瞭java平臺設計者對不同異常情況的分類,Exception是程序正常運行中,可以預料的意外情況,可能並且應該被捕獲,進行相應的處理。
  • Error是指正常情況下,不大可能出現的情況,絕大部分的Error都會導致程序處於非正常的、不可恢復的狀態。既然是非正常情況,不便於也不需要捕獲。常見的比如OutOfMemoryError之類都是Error的子類。
  • Exception又分為可檢查(checked)異常和不可檢查(unchecked)異常。可檢查異常在源代碼裡必須顯式的進行捕獲處理,這是編譯期檢查的一部分。不可檢查時異常是指運行時異常,像NullPointerException、ArrayIndexOutOfBoundsException之類,通常是可以編碼避免的邏輯錯誤,具體根據需要來判斷是否需要捕獲,並不會在編譯期強制要求。

面試題3:== 和 equals 的區別是什麼

正經回答:

  • == : 它的作用是判斷兩個對象的地址是不是相等。即,判斷兩個對象是不是同一個對象。(基本數據類型 == 比較的是值,引用數據類型 == 比較的是內存地址)
  • equals(): 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況:

情況1:類沒有覆蓋 equals() 方法。則通過 equals() 比較該類的兩個對象時,等價於調用瞭Object類的equals() 方法,也就是通過“==”比較這兩個對象。

// Object類中的equals() 方法
public boolean equals(Object obj) {
    return (this == obj);
}

情況2:類覆蓋瞭 equals() 方法。一般,我們都會覆蓋 equals() 方法來兩個對象的內容相等;若它們的內容相等,則返回 true (即,認為這兩個對象相等)。

// String類中的equals() 方法,已覆蓋,用於比較內容
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

深入追問:

追問1:如果我們不重寫equals() 方法,會怎麼樣?

舉例說明:

重點說明:是否重寫Object類中的equals方法,會對結果造成的影響

public static void main(String[] args) {
    // 字符串比較
    String a = "陳哈哈";
    String b = "陳哈哈";
    if (a == b) {// true  a==b
        System.out.println("a==b");
    }
    if (a.equals(b)) {// true  a.equals(b)
        System.out.println("a.equals(b)");
    }
    // StringBuffer 對象比較,由於StringBuffer沒有重寫Object的equal方法,因此結果出現錯誤
    StringBuffer c = new StringBuffer("陳哈哈");
    StringBuffer d = new StringBuffer("陳哈哈");
    if (c == d) {// false  c != d
        System.out.println("c == d");
    } else {
        System.out.println("c != d");
    }
    if (c.equals(d)) { // false 調用瞭Object類的equal方法
        System.out.println("StringBuffer equal true");
    }else {
        System.out.println("StringBuffer equal false");
    }
}
  • object的equals方法是比較的對象的內存地址,而String的equals方法比較的是對象的值。
  • 因為String中的equals方法是被重寫過的,而StringBuilder沒有重寫equals方法,從而調用的是Object類的equals方法,也就相當於用瞭 ==;

追問2:重寫equals的同時,我們需要重寫hashCode()方法麼?為什麼?

在重寫equals()方法時,也有必要對hashCode()方法進行重寫,尤其是當我們自定義一個類,想把該類的實例存儲在集合中時。  

hashCode方法的常規約定為:值相同的對象必須有相同的hashCode,也就是equals()結果為相同,那麼hashcode也要相同,equals()結果為不相同,那麼hashcode也不相同;

當我們使用equals方法比較說明對象相同,但hashCode不同時,就會出現兩個hashcode值,比如在HashMap中,就會認為這是兩個對象,因此會出現矛盾,說明equals方法和hashCode方法應該成對出現,當我們對equals方法進行重寫時,也要對hashCode方法進行重寫。

可以通過ide快捷鍵快速生成兩個方法,假設現在有一個學生Student類,其中有 age 和 name 兩個特征。生成代碼如下:

@Override
public boolean equals(Object o){
//首先比較兩個的地址值是否相同,如果相同,那內容也一定相同
	if(this == o) return true;
//如果o為空值或者兩個對象的類型是否相同,如果類型不同或者o為空值則內容一定不同
	if(o == null || getClass() != o.getClass()) return false;
//將object類型的實例強轉為Student類型
  	Student student = (Student)o;
//比較兩個實例的age是否相同
	if(age != student.age) return false;
//在比較name是否相同
	return name != null ? name.equals(student.name ) : student.name == null;
}
@Override
public int hashCode() {
	int result = age;
	result = 31 * result + (name != null ? name.hashCode() : 0);
	return result;
}

總結

本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: