java學習筆記24——String類不可變性

如果我們需要修改一個String對象,最好使用StringBuffer或StringBuilder。否則,會在每次創建一個新的String對象時消耗垃圾回收器大量時間。

How to read file content into a string?

The following is the Java code to do that. To make it work, the filePath need to be changed.

public static String readFileToString() throws IOException {
		File dirs = new File(".");
		String filePath = dirs.getCanonicalPath() + File.separator+"src"+File.separator+"TestRead.java";
 
		StringBuilder fileData = new StringBuilder(1000);//Constructs a string buffer with no characters in it and the specified 
                                                                  //initial capacity
		BufferedReader reader = new BufferedReader(new FileReader(filePath));
 
		char[] buf = new char[1024];
		int numRead = 0;
		while ((numRead = reader.read(buf)) != -1) {
			String readData = String.valueOf(buf, 0, numRead);
			fileData.append(readData);
			buf = new char[1024];
		}
 
		reader.close();
 
		String returnStr = fileData.toString();
		System.out.println(returnStr);
		return returnStr;
}


        By saying String, it is not exactly correct, it is actually called a StringBuffer in Java. Here is a post about Java String’s immutability. StringBuffer and StringBuilder are similar and they both are are for mutable strings. But StringBuffer is safe for use by multiple threads. The reason to use StringBuilder instead of StringBuffer here is that StringBuilder is faster, as it performs no synchronization.

        From programming usage perspective, String, StringBuffer, StringBuilder are all strings.

        上面英文是複製的一個老外的博客,沒有校正。

        首先,必須強調一點:String Pool不是在堆區,也不是在棧區,而是存在於方法區(Method Area)

        解析:

        String Pool是常量池(Constant Pool)中的一塊。

        我們知道,常量就是不可以再改變的值,給它建一個池子很明顯是爲了加快程序運行的速度;在一個程序中,常量和變量是相對存在的;變量因爲可變性所以一般存在於棧中,而常量去作爲一個特殊羣體被存在在常量池中。

        常量池(constant pool)指的是在編譯期被確定並被保存在已編譯的.class文件中的一些數據。--- (很明顯在方法區)

        它包括了關於類、方法、接口等中的常量,也包括字符串常量(這個就是Sring Pool啦)。

        在編譯好的class文件中,有個區域稱爲Constant Pool,它是一個由數組組成的表,類型爲cp_info constant_pool[],用來存儲程序中使用的各種常量,包括Class/String/Integer等各種基本Java數據類型。

======= ================

        上面這些,簡單理解:一個Class類,它裏面有常量的存在,比如 int a=10;String b="123450";它們在JVM看來就是常量(當然在方法中可能被修改啦),在Class被加載時,JVM特意都把它放在一個數組中維護起來,並且把該數組放在方法區中,起名叫常量池。

 

        常量池存在於方法區,它包含各種類型的常量(8個基本數據類型,包裝類型等)

        我們把常量池中的String Pool中的常量作爲對象來看待 --- 因爲String就是對象,String類型的常量自然也是對象啦!

比如:

        String str1 = new String("Hello");

        它創建了2個對象,一個是堆中的String對象,一個是String Pool中的String對象。

        要理解Java中String的運作方式,必須明確一點:String是一個非可變類(immutable)。什麼是非可變類呢?簡單說來,非可變類的實例是不能被修改的,每個實例中包含的信息都必須在該實例創建的時候就提供出來,並且在對象的整個生存週期內固定不變。Java爲什麼要把String設計爲非可變類呢?你可以問問 james Gosling :)。但是非可變類確實有着自身的優勢,如狀態單一,對象簡單,便於維護。其次,該類對象對象本質上是線程安全的,不要求同步。此外用戶可以共享非可變對象,甚至可以共享它們的內部信息。(詳見 《Effective java》item 13)。String類在java中被大量運用,甚至在class文件中都有其身影,因此將其設計爲簡單輕便的非可變類是比較合適的。

一、創建。
    好了,知道String是非可變類以後,我們可以進一步瞭解String的構造方式了。創建一個Stirng對象,主要就有以下兩種方式:
java 代碼
  1. String str1 = new String("abc");   //只是在堆裏面創建而不會添加到常量池  
  2. Stirng str2 = "abc";   //先用equals在常量池查找,有就返回給用戶,沒有就在堆裏創建,然後添加到常量池。

        雖然兩個語句都是返回一個String對象的引用,但是jvm對兩者的處理方式是不一樣的。對於第一種,jvm會馬上在heap中創建一個String對象,然後將該對象的引用返回給用戶。對於第二種,jvm首先會在內部維護的String Pool中通過String的 equals 方法查找是對象池中是否存放有該String對象,如果有,則返回已有的String對象給用戶,而不會在heap中重新創建一個新的String對象;如果對象池中沒有該String對象,jvm則在heap中創建新的String對象,將其引用返回給用戶,同時將該引用添加至String Pool中。注意:使用第一種方法創建對象時,jvm是不會主動把該對象放到String Pool
裏面的,除非程序調用 String的intern方法。看 下面的例子:

java 代碼
  1. String str1 = new String("abc"); //jvm 在堆上創建一個String對象   
  2.   
  3.  //jvm 在strings pool中找不到值爲“abc”的字符串,因此   
  4.  //在堆上創建一個String對象,並將該對象的引用加入至strings pool中   
  5.  //此時堆上有兩個String對象   
  6. Stirng str2 = "abc";   
  7.   
  8.  if(str1 == str2){   
  9.          System.out.println("str1 == str2");   
  10.  }
  11.  else{   
  12.          System.out.println("str1 != str2");   
  13.  }   
  14.   //打印結果是 str1 != str2,因爲它們是堆上兩個不同的對象   
  15.   
  16.   String str3 = "abc";   
  17.  //此時,jvm發現String Pool中已有“abc”對象了,因爲“abc”equals “abc”   
  18.  //因此直接返回str2指向的對象給str3,也就是說str2和str3是指向同一個對象的引用   
  19.   if(str2 == str3){   
  20.          System.out.println("str2 == str3");   
  21.   }
  22.   else{   
  23.          System.out.println("str2 != str3");   
  24.   }   
  25.  //打印結果爲 str2 == str3  

   再看下面的例子:

java 代碼
  1. String str1 = new String("abc"); //jvm 在堆上創建一個String對象   
  2.   
  3. str1 = str1.intern();   
  4. //程序顯式將str1放到String Pool中,intern運行過程是這樣的:首先查看String Pool   
  5. //有沒“abc”對象的引用,沒有,則在堆中新建一個對象,然後將新對象的引用加入至   
  6. //String Pool中。執行完該語句後,str1原來指向的String對象已經成爲垃圾對象了,隨時會   
  7. //被GC收集。   
  8.   
  9. //此時,jvm發現String Pool中已有“abc”對象了,因爲“abc”equals “abc”   
  10. //因此直接返回str1指向的對象給str2,也就是說str2和str1引用着同一個對象,   
  11. //此時,堆上的有效對象只有一個。   
  12. Stirng str2 = "abc";   
  13.   
  14.  if(str1 == str2){   
  15.          System.out.println("str1 == str2");   
  16.  }
  17.  else{   
  18.          System.out.println("str1 != str2");   
  19.  }   
  20.   //打印結果是 str1 == str2   
  21.   

 

    爲什麼jvm可以這樣處理String對象呢?就是因爲String的非可變性。既然所引用的對象一旦創建就永不更改,那麼多個引用共用一個對象時互不影響。


二、串接(Concatenation)。
     java程序員應該都知道
濫用String的串接操作符是會影響程序的性能的。性能問題從何而來呢?歸根結底就是String類的非可變性。既然String對象都是非可變的,也就是對象一旦創建了就不能夠改變其內在狀態了,但是串接操作明顯是要增長字符串的,也就是要改變String的內部狀態,兩者出現了矛盾。怎麼辦呢?要維護String的非可變性,只好在串接完成後新建一個String 對象來表示新產生的字符串了。也就是說,每一次執行串接操作都會導致新對象的產生,如果串接操作執行很頻繁,就會導致大量對象的創建,性能問題也就隨之而來了。
    爲了解決這個問題,jdk爲String類提供了一個可變的配套類,StringBuffer。使用StringBuffer對象,由於該類是可變的,串接時僅僅時改變了內部數據結構,而不會創建新的對象,因此性能上有很大的提高。針對單線程,jdk 5.0還提供了StringBuilder類,在單線程環境下,由於不用考慮同步問題,使用該類使性能得到進一步的提高。

三、String的長度
   我們可以使用串接操作符得到一個長度更長的字符串,那麼,String對象最多能容納多少字符呢?查看String的源代碼我們可以得知類String中是使用域 count 來記錄對象字符的數量,而count 的類型爲 int,因此,我們可以推測最長的長度爲 2^32,也就是
4G
    不過,我們在編寫源代碼的時候,如果使用 Sting str = "aaaa";的形式定義一個字符串,那麼雙引號裏面的ASCII字符最多只能有 65534 個。爲什麼呢?因爲在class文件的規範中, CONSTANT_Utf8_info表中使用一個16位的無符號整數來記錄字符串的長度的,最多能表示 65536個字節,而java class 文件是使用一種變體UTF-8格式來存放字符的,null值使用兩個字節來表示,因此只剩下 65536- 2 = 65534個字節。也正是變體UTF-8的原因,如果字符串中含有中文等非ASCII字符,那麼雙引號中字符的數量會更少(一箇中文字符佔用三個字節)。如果超出這個數量,在編譯的時候編譯器會報錯。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章