1.String類能被繼承嗎,爲什麼?
不能。類被final 修飾,不能被繼承。
2. String,Stringbuffer,StringBuilder的區別?
2.1我想從三個角度講下區別
2.11 是否可變? String 對象是不可變對象,每次進行字符串變量修改的時候都會新創建一個對象, 而StringBuffer StringBuilder 可變的字符串對象, 在原有的數據上進行修改。(反應在內存堆中的變化)
2.12 是否線程安全?
只有StringBuffer 線程安全的字符串對象,在方法上加了Synchronized
2.13 相加效率?
StringBuilder > Stringbuffer > String
2.2 使用場景的考慮
每次改變String的值,都要新創建一個對象,再將引用指向該對象,浪費內存空間,內存中的無用引用多了以後,jvm的GC就開始工作,系統就會慢了。
2.21.單線程下在字符串緩存區進行大量操作。 StringBuilder
2.22 多線程下在字符串緩存區進行大量操作。 StringBuffer 【實際開發中當中 基本不用,多線程下就考慮用鎖,而不是這貨】
2. 23 String 少量的字符串操作
2.3 談談String在內存中JVM 分佈
3.介紹String.intern()
JDK 1.7後,intern方法還是會先去查詢常量池中是否有已經存在,如果存在,則返回常量池中的引用,這一點與之前沒有區別,區別在於,如果在常量池找不到對應的字符串,則不會再將字符串拷貝到常量池,而只是在常量池中生成一個對原字符串的引用。簡單的說,就是往常量池放的東西變了:原來在常量池中找不到時,複製一個副本放到常量池,1.7後則是將在堆上的地址引用複製到常量池。
jdk1.7 調用的時候,常量池未有這個常量,把對應這個常量的引用拷貝到常量池 ,並返回這個指向 (堆) 的引用
jdk1.7之前的:(區別低版 本,拷貝副本到常量池,再返回 (常量池) 引用)
4.其他的一些面試題
1. 下面這段代碼的輸出結果是什麼?
String a = "hello2"; String b = "hello" + 2; System.out.println((a == b));
輸出結果爲:true。原因很簡單,"hello"+2在編譯期間就已經被優化成"hello2",因此在運行期間,變量a和變量b指向的是同一個對象。
2.下面這段代碼的輸出結果是什麼?
String a = "hello2"; String b = "hello"; String c = b + 2; System.out.println((a == c));
輸出結果爲:false。由於有符號引用的存在,所以 String c = b + 2;不會在編譯期間被優化,不會把b+2當做字面常量來處理的,因此這種方式生成的對象事實上是保存在堆上的。因此a和c指向的並不是同一個對象。javap -c得到的內容:
3.下面這段代碼的輸出結果是什麼?
String a = "hello2"; final String b = "hello"; String c = b + 2; System.out.println((a == c));
輸出結果爲:true。對於被final修飾的變量,會在class文件常量池中保存一個副本,也就是說不會通過連接而進行訪問,對final變量的訪問在編譯期間都會直接被替代爲真實的值。那麼String c = b + 2;在編譯期間就會被優化成:String c = "hello" + 2; 下圖是javap -c的內容:
4.下面這段代碼輸出結果爲:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
輸出結果爲false。這裏面雖然將b用final修飾了,但是由於其賦值是通過方法調用返回的,那麼它的值只能在運行期間確定,因此a和c指向的不是同一個對象
5.下面這段代碼的輸出結果是什麼?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
輸出結果爲(JDK版本 JDK6):
作者:wuxinliulei
鏈接:https://www.zhihu.com/question/29495158/answer/72534524
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
String 字符串常量 since JDK1.0
java.lang.String public final class String
StringBuffer 字符串變量(線程安全) since JDK1.0
java.lang.StringBuffer public final class StringBuffer
StringBuilder 字符串變量(非線程安全)since JDK1.5
java.lang.StringBuilder public final class StringBuilder
全部是lang包下的類,全部是final的類,不能被繼承。
------------------------------------------------------------------
簡要的說,String 類型和 StringBuffer 類型的主要性能區別其實在於 String 是不可變的對象, 因此在每次對 String 類型進行改變的時候其實都等同於生成了一個新的 String 對象,然後將指針指向新的 String 對象,所以經常改變內容的字符串最好不要用 String ,因爲每次生成對象都會對系統性能產生影響,特別當內存中無引用對象多了以後, JVM 的 GC 就會開始工作,那速度是一定會相當慢的。(曾經有朋友遇到過打日誌使用+號的字符串導致頻繁Full GC的)
而如果是使用 StringBuffer 類則結果就不一樣了,每次結果都會對 StringBuffer 對象本身進行操作,而不是生成新的對象,再改變對象引用。
所以在一般情況下我們推薦使用 StringBuffer ,特別是字符串對象經常改變的情況下。而在某些特別情況下, String 對象的字符串拼接其實是被 JVM 解釋成了 StringBuffer 對象的拼接,所以這些時候 String 對象的速度並不會比 StringBuffer 對象慢,而特別是以下的字符串對象生成中, String 效率是遠要比 StringBuffer 快的:
String S1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
你會很驚訝的發現,生成 String S1 對象的速度簡直太快了,而這個時候 StringBuffer 居然速度上根本一點都不佔優勢。其實這是 JVM 的一個把戲,在 JVM 眼裏,這個
String S1 = “This is only a” + “ simple” + “test”; 其實就是:
String S1 = “This is only a simple test”; 所以當然不需要太多的時間了。但大家這裏要注意的是,如果你的字符串是來自另外的 String 對象的話,速度就沒那麼快了,譬如:
String S2 = "This is only a";
String S3 = "simple";
String S4 = "test";
String S1 = S2 +S3 + S4;
這時候 JVM 會規規矩矩的按照原來的方式去做
在大部分情況下
StringBuffer > String
StringBuffer
Java.lang.StringBuffer線程安全的可變字符序列。一個類似於 String 的字符串緩衝區,但不能修改。雖然在任意時間點上它都包含某種特定的字符序列,但通過某些方法調用可以改變該序列的長度和內容。
可將字符串緩衝區安全地用於多個線程。可以在必要時對這些方法進行同步,因此任意特定實例上的所有操作就好像是以串行順序發生的,該順序與所涉及的每個線程進行的方法調用順序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重載這些方法,以接受任意類型的數據。每個方法都能有效地將給定的數據轉換成字符串,然後將該字符串的字符追加或插入到字符串緩衝區中。append 方法始終將這些字符添加到緩衝區的末端;而 insert 方法則在指定的點添加字符。
例如,如果 z 引用一個當前內容是“start”的字符串緩衝區對象,則此方法調用 z.append("le") 會使字符串緩衝區包含“startle”,而 z.insert(4, "le") 將更改字符串緩衝區,使之包含“starlet”。
在大部分情況下 StringBuilder > StringBuffer
java.lang.StringBuilder
java.lang.StringBuilder一個可變的字符序列是5.0新增的。此類提供一個與 StringBuffer 兼容的 API,但不保證同步。該類被設計用作 StringBuffer 的一個簡易替換,用在字符串緩衝區被單個線程使用的時候(這種情況很普遍)。如果可能,建議優先採用該類,因爲在大多數實現中,它比 StringBuffer 要快。兩者的方法基本相同。
StringBuffer:
是一個可變對象,當對他進行修改的時候不會像String那樣重新建立對象
它只能通過構造函數來建立,
StringBuffer sb = new StringBuffer();
note:不能通過付值符號對他進行付值.
sb = "welcome to here!";//error
對象被建立以後,在內存中就會分配內存空間,並初始保存一個null.向StringBuffer
中付值的時候可以通過它的append方法.
sb.append("hello");
並且由於String 對象是不可變對象,每次操作Sting 都會重新建立新的對象來保存新的值.
這樣原來的對象就沒用了,就要被垃圾回收.這也是要影響性能的.
StringBuilder:
是一個可變對象,和StringBuffer相比,不是線程安全的,一般用在單個線程操作的時候(這種情況很普遍,所以一般優先選用StringBuilder),速度比StringBuffe快很多。
字符串連接操作中StringBuffer的效率要比String高:
String str = new String("welcome to ");
str += "here";
的處理步驟實際上是通過建立一個StringBuffer,讓侯調用append(),最後
再將StringBuffer toSting();
這樣的話String的連接操作就比StringBuffer多出了一些附加操作,當然效率上要打折扣.
看看以下代碼:
將26個英文字母重複加了5000次,
String tempstr = "abcdefghijklmnopqrstuvwxyz";
int times = 5000;
long lstart1 = System.currentTimeMillis();
String str = "";
for (int i = 0; i < times; i++)
{
str += tempstr;
}
long lend1 = System.currentTimeMillis();
long time = (lend1 - lstart1);
System.out.println(time);
可惜我的計算機不是超級計算機,得到的結果每次不一定一樣一般爲 46687左右。
也就是46秒。
我們再看看以下代碼
String tempstr = "abcdefghijklmnopqrstuvwxyz";
int times = 5000;
long lstart2 = System.currentTimeMillis();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < times; i++)
{
sb.append(tempstr);
}
long lend2 = System.currentTimeMillis();
long time2 = (lend2 - lstart2);
System.out.println(time2);
得到的結果爲 16 有時還是 0
所以結論很明顯,StringBuffer 的速度幾乎是String 上萬倍。當然這個數據不是很準確。因爲循環的次數在100000次的時候,差異更大。不信你試試。
根據上面所說:
str += "here";
的處理步驟實際上是通過建立一個StringBuffer,然後調用append(),最後
再將StringBuffer toSting();
所以str += "here";可以等同於
StringBuffer sb = new StringBuffer(str);
sb.append("here");
str = sb.toString();
所以上面直接利用"+"來連接String的代碼可以基本等同於以下代碼
String tempstr = "abcdefghijklmnopqrstuvwxyz";
int times = 5000;
long lstart2 = System.currentTimeMillis();
String str = "";
for (int i = 0; i < times; i++) {
StringBuffer sb = new StringBuffer(str);
sb.append(tempstr);
str = sb.toString();
}
long lend2 = System.currentTimeMillis();
long time2 = (lend2 - lstart2);
System.out.println(time2);
平均執行時間爲46922左右,也就是46秒。
總結: 如果在程序中需要對字符串進行頻繁的修改連接操作的話.使用StringBuffer性能會更好