昨天申請了一個LeetCode的賬號,先刷了一題最基礎的,字符串逆序輸出。
我先寫出了如下代碼:
public class Solution { public String reverseString(String s) { String rev = ""; for(int i = s.length()-1; i >= 0; i--){ rev += s.charAt(i); } return rev; } }
這份代碼在OJ上運行的結果是Time Limit Exceeded,也就是超時了,顯然效率太低。
我又改成如下代碼,把String類換成StringBuffer類:
public class Solution { public String reverseString(String s) { StringBuffer rev = new StringBuffer(); for(int i = s.length()-1; i >= 0; i--){ rev.append(s.charAt(i)); } return rev.toString(); } }
這份代碼可以通過,並且用時只有6ms。
為什么String類和StringBuffer類的效率會相差這么多呢?我上網查了資料,主要是以下原因。
String類的對象其實是一個常量,例如String s = "abc"; 那么s其實是一個常量,它的值就是"abc",不能改變。
Java所提供的String操作方法,比如直接進行 '+' 運算,其實是作了封裝,給了使用者能對String對象進行增加或刪除的錯覺,本質上並不是如此。
當我們執行 s += "def"; 時,並不是說 s 的內容直接由"abc"變為了"abcdef","abc"仍是"abc","def"會被暫時存儲到一塊新的內存里,然后再開辟一個新的內存空間,把"abc"和"def"拷貝到這個內存空間,然后 s 指向這個新的內存空間。
這個過程其實就是new了一個新的String對象,新對象是"abcdef",s 拋棄了原來的對象,指向了新對象。
由於每次操作都要重新new一個對象,並且占用大量內存,因此String類的效率不高。
那么StringBuffer呢?
StringBuffer對象是一個長度可變的對象,當對StringBuffer的進行增加或刪除時,不需要new一個對象,可以動態地修改堆內存。
StringBuffer提供了append方法,在字符串的末尾進行添加。實際上,String類進行 '+' 運算,就是創建了一個StringBuffer對象,然后調用append進行添加,最后調用toString把StringBuffer轉化為String。
顯然,String的效率絕對不如StringBuffer了。
可以再看一個直觀的測試樣例:
public static void main(String[] args){ String toappend = "abcdefghijklmnopqrstuvwxyz";//26個字母,用於每次添加 int times = 20000;//添加20000次 //使用String類,將26個字母添加20000次 long start = System.currentTimeMillis(); String str = ""; for(int i = 0; i < times; i++){ str += toappend; } long end = System.currentTimeMillis(); System.out.println("String time = " + (end-start)+" ms");
//使用StringBuffer類,將26個字母添加20000次 start = System.currentTimeMillis(); StringBuffer strbuf = new StringBuffer(); for(int i = 0; i < times; i++){ strbuf.append(toappend); } end = System.currentTimeMillis(); System.out.println("StringBuffer time = " + (end-start)+" ms"); }
運行結果: