《Java程序猿面試寶典》之字符串


前不久剛看完這一章,然而這遺忘速度實在是不能忍,既然總是遺忘,那么老衲就和你磨上一磨。

1.字符串基礎

先說字符串吧,看例1:

1 String a = "abc";
2 String b = "abc";
3 a==b; //true
4 a.equals(b) //true

再來看看例2:

String a = new String("abc");
String b = new String("abc");
a==b; //false
a.equals(b); //true

在例1中,"abc"是放在常量池(Const pool)中的,所以雖然a,b都等於"abc",但是內存中只有一份副本,所以"==" 返回true。"abc"是編譯期常量,編譯時已經能夠確定它的值,在編譯好的class文件中,它已經在String Pool中了, String a = "abc"; 會在String Pool中查找等於"abc"的字符串(用equals),若存在就把引用返回,若不存在就會創建一個"abc"放在String Pool中,然后把引用返回。

在例2中,new方法決定了兩個不同的String "abc"被創建放在了內存heap區,分別被a,b指向,因此,"==" 返回了false;這里要注意的一點是:Const Pool存儲在Method Area中,而不是堆中。所以,我們可以看看例3:

1 String s1 = new String("aaa777");
2 String s2 = "aaa777";
3 System.out.println(s1==s2);//結果為false

在Java中,使用new關鍵字會創建一個新對象,在本例中,不管在String Pool中是否已經有值相同的對象,都會創建一個新的String對象存儲在heap中,然后返回其引用。s2指向的對象存儲在String Pool中,他們肯定不是同一個對象,只不過存儲的字符串相同罷了,所以返回的結果必然是false。

下面我們再來延伸一下:

String s = "a" + "b" + "c" + "d" + "e";

這里一共創建了幾個對象?A.沒有創建 B.1個對象 C.2個對象 D.3個對象

答案是:B   要注意的是,賦值語句后面部分的"a"、"b"、"c"、"d"、"e"都是常量,對於常量,編譯時就直接存儲他們的字面值,而不是它們的引用,在編譯時就直接將它們連接的結果提取出來變成了"abc"。關於這個String類重載的連接符,后面我們還會講到,這里先點到為止。再說一點,String使用private final char value[]來實現字符串的存儲,也就是說String對象創建之后,就不能再修改此對象中存儲的字符串內容,就是因為如此,才說String類型是不可變的(immutable)。

再看例4:

1 String s1 = new String("aaa777");
2 s1 = s1.intern();
3 String s2 = "aaa777";
4 System.out.println(s1==s2);

你猜一猜這個最終的執行結果會是什么?是true!沒錯,是true!

當調用intern方法時,如果String Pool中已經包含一個等於此String對象的字符串(用equals確定),則返回池中的字符串,否則將此String對象添加到池中,並返回此String對象在String Pool中的引用。由於執行了 s1 = s1.intern(); ,會使s1指向String Pool中值為"aaa777"的字符串對象,s2也指向了同樣的對象,所以結果為true.

例5:

1 String s1 = new String("777");
2 String s2 = "aaa777";
3 String s3 = "aaa"+"777";
4 String s4 = "aaa" + s1;
5 System.out.println(s2==s3); //true
6 System.out.println(s2==s4); //false
7 System.out.println(s2==s4.intern()); //true

顯然,行5和行7結果就不需要我來講了,問題在行6。為什么行6的結果是false呢?由於s1是變量,在編譯期不能確定它的值是多少,所以會在執行的時候創建一個新的String對象存儲到heap中,然后賦值給s4!注意,是heap中,而s2在Const Pool中,顯然s2和s4的引用值自然是不相等的。

好了,字符串基礎快講完了,有點長,最后我們再看一個函數結束吧~

1 String str = "ABCDEFGH";
2 String str1 = str.substring(3,5);
3 System.out.println(str1);

猜猜結果是多少?"DEF"嗎?差點對=。=  這里要注意啊,java中的substring是前包括后不包括的,所以應該是"DE"。

2.StringBuffer

 例1:

1 String result = "hello" + "world";
2 StringBuffer result = new StringBuffer.append("hello").append("world");

行1的效率好於行2,這是因為JVM會進行如下處理:

  • 將result字符串進行"hello"+"world"處理,然后才賦值給result,只開辟了一次內存段。
  • 編譯StringBuffer后還要進行append處理,開辟了一次內存,擴展了2次,花的時間要長一些。

例2:

1 public String getString(String s1,String s2){
2     return s1+s2;
3 }
4 
5 public String getString(String s1,String s2){
6      return new StringBuffer().append(s1).append(s2);
7 }

這兩個的效率是一樣的,都是先開辟一個內存段,再合並(擴展)內存,所以兩者執行的過程是一致,效率相當。

例3:

(1)String s = "s1";
           s+="s2";
           s+="s3";
(2) StringBuffer s = new StringBuffer().append("s1").append("s2").append("s3");

(2)的效率好於(1),因為String是不可變對象,每次"+="操作都會構造新的String對象,實際上是另外創建了一個對象,而原來指向的那個對象就成了垃圾,比如如下代碼:

1 String tmp = "";
2 for(int i =0;i<9999;tmp += "x"){}

一個循環就產生了n個對象,從而造成內存和時間的浪費。

例4:

1 (1)StringBuffer s = new StringBuffer();
2     for(int i=0;i<50000;i++){
3          s.append("hello");
4       }
5 (2)StringBuffer s = new StringBuffer(250000);
6      for(int i=0;i<50000;i++){
7           s.append("hello");
8        }

(2)的效率好於(1),因為StringBuffer內部實現的是char數組,默認初始化長度為16,每當字符串長度大於char數組長度的時候,JVM會構造更大的新數組,並將原先的數組復制到新數組,(2)避免了數組復制的開銷。

最后再看一個例子:

例5,以下程序創建了幾個對象?A. 4 B. 3 C. 5 D. 6

1 String A,B,C;
2 A = "a";
3 B = "b";
4 A = A + B;
5 StringBuffer D = new StringBuffer("abc");
6 D = D.append("567");

首先,我們先搞清幾個概念:

  • 引用變量與對象。A aa; 語句聲明一個類A的引用變量aa(常稱為句柄),而對象一般通過new創建。所以題目中D僅僅是一個引用變量,他不是對象。而字符串"abc"是一個String對象
  • Java中所有的字符串文字(字符串常量)都是一個String的對象。有人在一些場合喜歡把字符串當做字符數組,因為字符串與字符數組存在一些內在的聯系。事實上,它與字符數組是兩種完全不同的對象。如System.out.println("Hello".length()); 這里length()顯然是對象的方法,而char[] cc = {'H','i'};System.out.println(cc.length); 這里的cc.length則是數組的屬性,要注意區分。

字符串對象的創建。由於字符串對象的大量的使用,Java中為了節省內存空間和運行時間,在編譯階段就把所有的字符串文字放到一個文字池(pool of literal strings)中,而運行時文字池成為常量池的一部分。文字池的好處就是該池中所有相同的字符串常量被合並,只占用一個空間。我們來看一段代碼:

1 String s1 = new String("abc");
2 String s2 = new String("abc");

String s1 = new String("abc");語句,這里"abc"本身就是pool中的一個對象,而在運行時執行new String()時,將pool中的對象復制一份放到heap中,並且把heap中的這個對象的引用交給s持有。這條語句就創建了2個String對象。於是,上面的兩行代碼創建了3個對象,pool中一個,heap中2個!

OK,我們現在來對例5進行一下解析。

 

 StringBuffer D = new StringBuffer("abc"); 產生了兩個對象,"abc"本身與經過new創建出來的不是一個對象。 A=A+B; 此處創建了一個對象,並由引用A來引用,那么原來A所指向的對象就成為了垃圾,被回收。StringBuffer的特點是改變對象本身而不是創建新的對象,因此,此處 D= D.append("567"); 都是對同一個對象進行處理。所以整個例5一共創建了1+1+1+2=5個對象,答案是C

3.正則表達式

例1: String s = "32fdsfd8fds0fdsf9323k32k" ,從中找出3280932332,你會怎么做?

一般做字符串替換,我們能想到的一般方法都是正則表達式。所以可以這樣: String a = s.replaceAll("[^0-9]",""); ,"[^0-9]"是正則表達式,表示除了0到9以外的字符,這條代碼的意思是將s中所有非0-9的字符替換為空串。

例2: String str = "2006-04-15 02:31:04" ,要把這個串變成20060415023104,你會怎么做?

首先,這個簡單的任務可以用最笨的方法:

 str = str.replaceAll("-","");str = str.replaceAll(":","");str = str.replaceAll(" ",""); ,不過這有點太low逼了,看下面的方法:

 1 class Test{
 2     public static void main(String[] args){
 3         String  str = "2006-04-15 02:31:04";
 4         String str2 = "";
 5         String[] result = str.split("\\D");
 6         for(int i=0;i<result;i++){
 7             System.out.print(result[i]);
 8             str2 += result[i];
 9         }
10         System.out.println(str2);
11     }
12 }

這里"\\D"表示非數字字符。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM