前不久剛看完這一章,然而這遺忘速度實在是不能忍,既然總是遺忘,那么老衲就和你磨上一磨。
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"表示非數字字符。