Java面試題沖刺第一天–基礎篇1
面試題1:Java 中操作字符串都有哪些類?它們之間有什麼區別? 正經回答:
操作字符串的類有:String
、StringBuffer
、StringBuilder
。
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)添加字符。
- StringBuilder一個可變的字符序列是JDK1.5新增的。此類提供一個與 StringBuffer 兼容的 API,但不保證同步。該類被設計用作 StringBuffer 的一個簡易替換,用在字符串緩沖區被單個線程使用的時候(這種情況很普遍)。如果可能,建議優先采用S
- 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的更多內容!
推薦閱讀:
- Java字符串常見的操作(比較,查找,替換等)
- Java中如何正確重寫equals方法
- Java有哪些操作字符串的類?區別在哪?
- 帶你瞭解10道java入門面試題
- Java如何重寫object類的equals方法詳解