[轉]Java裏的字符串, String類簡單介紹

一, Java裏的字符串.

首先聲明:html

1.1 字符串跟String類是不一樣的概念

 

本文涉及兩個重點,  1個是字符串, 1個是String類. 它們雖然有聯繫, 可是倒是徹底不一樣的兩個概念!java

 

咱們能夠參考jdk api中文裏對String類的解釋:
面試

public final class String
extends Object
implements Serializable, Comparable<String>, CharSequence
編程

 

String 類表明字符串。Java 程序中的全部字符串字面值(如 "abc" )都做爲此類的實例實現。
字符串是常量;它們的值在建立以後不能更改。字符串緩衝區支持可變的字符串。由於 String 對象是不可變的,因此能夠共享。
api

 

由上面的解析咱們見到幾個不易理解的地方:多線程

例如: 函數

字符串是常量?this

它們的值不能改?編碼

由於..因此..  這什麼邏輯?.net

 

在實際編程當中, 咱們以爲字符串變量的值能夠更改的呀?

 

本人認爲, 你們不必擔憂本身的理解能力, 中文jdk api的翻譯實在是很沒有節操的.

上面解釋的最後一句話的原文是這樣的:

Strings are constant; their values cannot be changed after they are created.String buffers support mutablestrings. Because String objects are immutable they can be shared

 

本人渣翻:

字符串是常量; 它們的值一旦建立不能更改. 然而String類的引用(變量 or 內存)卻能夠指向不一樣的字符串. 是由於字符串對象雖然是不能修改的, 可是它們的地址能夠共享.

 

原文和本人翻譯都有兩種顏色的單詞,  紅色就是指上面第一個概念字符串.  而藍色指的另1個概念String類.

相信即便本人修改了翻譯, 仍然會有人以爲仍是不能理解, 請往下看:

 

 

 

1.2 java裏字符串的定義

 

注意這個不是String類的定義哦, 定義以下:

Java裏的字符串就是存放於數據區(靜態區)以Unicode編碼的字符集合.

可見java裏的字符串跟c語言的字符串以下兩個本質上的區別:

 

1.2.1 Java裏字符串用Unicode編碼

c語言中的字符串裏面的字符串是用ASCII編碼的, ASCII只用1個字節(byte)的內存表示1個字符. 可是1個字節的內存數量不足以表示全世界那麼多種字符.

例如1個漢子就須要2個字節來表示.

 

因此c語言裏的某些字符處理函數, 若是參數傳入1個漢字可能就會出錯, 由於畢竟字符的佔用內存長度不一樣.

 

而Unicdoe也叫萬國碼, 它用兩個字節去表示任何1個字節, 不管是字母仍是漢字. 因此利用java來作內存處理更加方便, 跨平臺性很是好.

缺點就是比c語言字符處理更加耗內存.

 

 

 

1.2.2 Java裏字符串存放在數據區(靜態區).

 

我以前的博文見過, java程序相似於c語言, 運行時會把程序佔用的內存大體分割成幾個部分.

分別是

stuck(棧區), Heap(堆區), Data(數據區)和代碼區 

其中數據區用於存放靜態變量和字符串常量.

見下圖

 

 

一, Java裏的字符串.

 

 

1.3 爲何說java裏的字符串是常量, 不可修改的.

 

1.3.1 通常的類指向的是變量

 

關於這點, 須要對比才能講得清楚.

這裏咱們利用1個自定的類來舉個例子:

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. package String_kng;  
  2.   
  3. class Human_1{  
  4.     int id;  
  5.     int age;  
  6.     public Human_1(int id, int age){  
  7.         this.id = id;  
  8.         this.age = age;  
  9.     }  
  10.     public String toString(){  
  11.         return "id is " + id + ","  + " age is " + age;  
  12.     }  
  13. }  
  14.   
  15. public class String_2{  
  16.     public static void f(){  
  17.         Human_1 h = new Human_1(1,30);  
  18.         Human_1 h2 = h; //  
  19.         System.out.printf("h: %s\n", h.toString());   
  20.         System.out.printf("h2: %s\n\n", h.toString());   
  21.   
  22.         h.id = 3;  
  23.         h.age = 32;  
  24.         System.out.printf("h: %s\n", h.toString());   
  25.         System.out.printf("h2: %s\n\n", h.toString());   
  26.   
  27.         System.out.println( h == h2 );  
  28.     }  
  29. }  


上面例子中定義了1個Human_1的類, 只有2個成員id和age.

 

下面f()中首先實例化了1個Human_1的對象h. 

而後定義另外1個引用h2, 而後把h的地址賦予給h2 ( Human_1 h2 = h)

而後輸出h, h2的值, 它們是同樣的.

 

而後修改h的值, 

再次輸出h h2的值, 發現h2的值也被修改.

最後用 " == " 來比較h 和 h2所指向的地址.

明顯它們二者所指向的地址是相同.

 

輸出:

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. [java] h: id is 1, age is 30  
  2. [java] h2: id is 1, age is 30  
  3. [java]   
  4. [java] h: id is 3, age is 32  
  5. [java] h2: id is 3, age is 32  
  6. [java]   
  7. [java] true  

 

其實上面例子能夠理解成:

首先用1個容器h2 來保存 h1所指向的地址.

而後修改h1的值, 最後h1的地址沒變化.

 

也就是說:

h這個對象雖然值被修改了, 可是指向的內存地址沒有變化, 變的是該內存的內容(值)

 

畫張圖便於理解:

 

如上圖可見:

1  對象名h 和 h2 自己是都是局部變量, 位於棧區中, 裏面存放的是1個地址.

2. 該地址指向的是堆區的一塊內存, 這塊內存是用new Human()劃分出來的. 並且把頭部地址賦予對象名h

3. 該內存包括兩個部分, 1個用於存放成員id的值, 另1個存放成員age的值.

 

可見, 不管對象h成員的值如何改變, 變的只是堆區內存的存放內容, 而堆區內存地址是沒變化的. 對象引用h的指向也沒有變化.

咱們通常把這種內存地址不變, 值能夠改變的東西成爲變量.

意思就是內存地址不變的前提下內存的內容是可變的.

 

注意, 上面的例子不說是對象引用h是1個變量,  而是說h指向的內存是1個變量

 

1.3.2 java裏的字符串是常量

 

將上面的例子改一下, 把Human_1類改爲String類:

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. package String_kng;  
  2.   
  3. public class String_3{  
  4.     public static void f(){  
  5.         String s = "cat";  
  6.         String s2 = s;  
  7.   
  8.         System.out.printf("s: %s\n", s);   
  9.         System.out.printf("s2: %s\n", s2);   
  10.         System.out.println(s == s2);   
  11.   
  12.         s = "dog";  
  13.         System.out.printf("\ns: %s\n", s);   
  14.         System.out.printf("s2: %s\n", s2);   
  15.         System.out.println(s == s2);   
  16.     }  
  17. }  

 

邏輯跟上面的例子基本沒區別, 也是首先實例化1個String對象s, 它的值是s;

而後將s所指向的地址保存在另個引用s2.

 

這時輸出s 和 s2的值, 它們固然是相等的.

這時"修改"s的值爲"dog"

再輸出s 和 s2的值, 卻發現s的值變成dog了, 可是s2的值仍是cat..  並且它們的所指向的地址也再也不相等.

輸出結果:

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. [java] s: cat  
  2. [java] s2: cat  
  3. [java] true  
  4. [java]   
  5. [java] s: dog  
  6. [java] s2: cat  
  7. [java] false  

 

 

爲何s 和 s2所指向的地址一開始是相等的, 一旦s的修改成dog後, s 和 s2所指向的地址就不等呢.

緣由就是這句代碼:

s = "dog";  

並非修改s所指向的內存地址, 而是改變了s的指向, 也就是修改了s的所指向的地址啊.

 

畫兩張圖:

s的值"修改"前:

 

由上圖可見:

1. String類也是java的類, 因此它的實例化對象也須要在堆區劃份內存。

2. 兩個對象引用s 和 s2這時都指向了堆區同1塊對象內存。因此它們的所指向地址是相等的。

3. 字符串真正的地址不是再堆區中, 是在數據區中的。 而堆區對象內存中有其中1個對象成員保存了該字符串在數據區的真正地址

 

s的值"修改"爲dog後:

 

由上圖可見:

1. s = "dog" 並非修改s所指向的內容. 而是在堆區和數據區各劃分了1個新的內存. 其中數據區劃分1個新的字符串"dog" , 堆區劃分1個新的String對象內存, 保存了dog的字符串地址.

2. 固然以前那個堆區對象內存和數據"cat"的內存是由 String s = "cat" 這條語句建立,關於String類語法機制後面會再講。

3. s = "dog" 不但在數據區和堆區都各自建立1個新內存, 並且還改變了本身所指向的地址, 因此這時s 和 s2 再也不相等.

4. 關鍵是原來數據的字符"cat" 並無被修改! 也不可能被修改.

 

咱們通常把這種內存值不能改變, 只能經過引用去指向另1塊的東西叫作常量.

 

 

1.3.3 java裏字符串不能修改的一些小結.

 

其實從另一些方面一也能體現出java字符串不能修改的.

例如一些java的內部類, 如Calendar(日期)通常都會提供一些setXXXX的方法讓程序猿去修改對應的值. 例如 setYear(), setDate().等等

而String類是沒有這類setXXXX方法.

 

雖然字符串在數據區中的內存不能修改, 可是咱們能夠爲String類的對象指向另外一塊內存. 因此這個特性在編程形成的影響不大.

那麼原來的內存怎麼辦呢? 放心, java的內存回收機制會收拾它們的..

 

 

 

二, Java裏的String類.

 

2.1 java裏 String 類的 本質

 

String類的書面解釋在本文的1.1 章裏提升過了, 是1個用於字符串的類.

可是這個解釋並無指明String類的本質.

 

咱們知道, Java類的本質大體上能夠理解爲 成員(屬性) 和 方法的集合體.

String類也同樣, 只不過String類有1個關鍵的成員, 這個成員保存着數據區的某個字符串的內存地址. 能夠理解爲1個指針.

而String類的方法是一些對對應字符串的查詢方法(例如indexOf(), charAt()等). 注意, 並無對這個字符串進行修改的方法哦, 字符串是常量, 不能修改.

雖然String類不能修改字符串, 可是上面保存字符串地址的成員倒是能夠被改變的, 也就是說String類的對象能夠指向另1個字符串.

 

畫個圖:

 

 

見上圖, java的String類實例化1個對象後, 會在堆區劃分一塊對象的內存, 其中1個關鍵成員存放的是數據區字符串的地址.

而下面若干個方法內存, 存放的是該函數(方法)在代碼區的2進制代碼的地址.

 

 

 

2.2 String類實例化對象的第一個方法. new String("abc")

 

固然, String類的構造函數有不少個(參數不一樣), 可是在coding中,經常使用的實例化對象方法無非是兩種.

第一種就是與其餘類同樣, 利用構造方法.

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. String s = new String("abc");  


上面的代碼作了下面若干個事情.

1. 在數據區中劃分一塊內存存放字符串, 值是"abc", 這塊內存一旦建立, 值"abc" 不能被修改.

2. 在堆區劃分1塊對象內存, 其中小塊用於存放上面字符串的地址, 另外一些用於存放函數指針.

3. 在棧區劃分一塊內存, 存放上面堆區的頭部地址.

 

下面是1個例子:

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. package String_kng;  
  2.   
  3. public class String_4{  
  4.     public static void f(){  
  5.         String s = new String("cat");  
  6.         String s2 = new String("cat");  
  7.   
  8.         System.out.printf("s: %s\n", s);   
  9.         System.out.printf("s2: %s\n", s2);   
  10.         System.out.println(s == s2);   
  11.         System.out.println(s.equals(s2));  
  12.     }  
  13. }  

 

上面利用new 實例化了兩個對象s和s2 , 它們所指向的字符串值都是"cat"

而後用 "==" 和 equals來比較二者

輸出:

 

[plain]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. [java] s: cat  
  2. [java] s2: cat  
  3. [java] false  
  4. [java] true  

 

 

可見用equals 來比較s 和 s2, 它們是相等的, 由於它們的內容相同. 並且equals方法在String類裏重寫過了.

而用 "==" 比較的是兩個對象s 和 s2所指向的地址, 它們所指向的地址是不一樣的. 

以下圖:

 

亦即系講, 兩個new語句分別在數據區和堆區各自都劃分2個內存.

數據區中有兩個字符串內存, 它們的值是同樣的都是"cat".

堆區有兩個對象內存, 它們分別保存了各自對應的字符串地址.

而stuck區中兩個s1 s2 保存了各自的堆區內存地址.  這兩個地址明顯是不一樣的. 也就是 s == s2 返回false的緣由.

 

 

 

2.3 String類實例化對象的另外一個方法.  = "abc"

 

事實上, 咱們在編程中新建1個字符串更多狀況下會用以下的方式:

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. String s = "abc";  

 

這種方式更上面那種有什麼區別呢?

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. package String_kng;  
  2.   
  3. public class String_5{  
  4.     public static String g(){  
  5.         String s4 = "cat";  
  6.         return s4;  
  7.     }  
  8.   
  9.     public static void f(){  
  10.         String s = new String("cat");  
  11.         String s2 = "cat";  
  12.         String s3 = "cat";  
  13.         System.out.printf("s: %s\n", s);   
  14.         System.out.printf("s2: %s\n", s2);   
  15.         System.out.printf("s3: %s\n", s3);   
  16.           
  17.         System.out.println(s == s2);   
  18.         System.out.println(s2 == s3);   
  19.         System.out.println(s2 == g());   
  20.     }  
  21. }  




這個例子步驟也不復雜:

 

 

首先f()方法裏 利用第一種方法實例化了1個值爲"cat"的對象s

而後裏利用第二種方法 又 建立了兩個String 對象s2 和 s3, 它們的值都是"cat".

而後用"==" 來比較它們.

而後f()方法調用g()方法, g()方法利用第二方式實例化了1個值爲"cat"的String 對象s4 
最後用 "==" 比較s2 和 s4 的地址.

 

 

輸出:

 

[plain]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. [java] s: cat  
  2. [java] s2: cat  
  3. [java] s3: cat  
  4. [java] false  
  5. [java] true  
  6. [java] true  

 

由結果得知, s4 和 s2 和 s3的地址是相同的! 而由第一種方法建立的s 跟前面三者地址不一樣.

 

因此結論以下:

利用 = "cat" 方式建立1個String對象時, java 首先會檢測當前進程的數據區是否有1個以相同方式建立的值是同樣的字符串存在.

若是無, 則相似 new Sring("cat")方式, 在數據區和堆區都各自劃分一塊新內存, 用於該建立的對象.

若是有, 則直接把該對象的地址指向 已存在的堆區內存地址.

 

也就是講, 在f() 裏的String s2 = "cat" 至關於執行了 String s2 = new String("cat");

而在f()裏的 String   s3 = "cat" 至關執行了String s3 = s2;

而在g()裏, 理論上g()是不能訪問f()裏的 局部變量的, 可是g()仍是檢測到數據區存在用相同方式建立並且值1個樣的字符串.

因此s4 也指向了堆區的那一塊內存.

 

以下圖:

 

這個例子說明了, 在同1個java程序中, 全部用 " = "abc" " 方式建立的並且具備相同值的多個String對象其實都是同1個對象. 由於它們指向同一塊堆區的內存.

因爲這種特性, 因此這種用" = "abc"" 方式建立的對象十分適合作 synchronized對象鎖 要鎖的對象.   不用擔憂鎖的是兩個不一樣的對象致使 多線程同步失敗.

 

 

 

三, String類的經常使用方法.

 

下面是應付面試的, 你們看看就好.

1. 

public char charAt(int index) //返回字符串中第index個字符

 

2. 

public int length()  //返回字符串的長度

 

3.

public int indexOf(String str)

返回字符串中出現str的第1個位置

 

4.

public int indexOf(String str, int fromIndex)

返回字符串中, 從第fromIndex個字符數起, 出現str的第1個位置, 這個方法是上面方法的重載

 

5.

public boolean equalsIgnoreCase(String str)

忽略大小寫, 比較兩個字符是否相等.

 

6.

public String replace(char oldChar, char newChar)

返回1個新字符串, 該新字符串內的oldChar被newChar替換掉, 注意舊字符串沒有被修改.

 

7.

public boolean startsWith(String prefix)

判斷字符串是否以 prefix 開頭

 

8.

public boolean endsWith(String suffix)

判斷字符產是否以suffix 結尾

 

9.

public String subString(int beginIndex)

截取從第beginIndex個字符開始到最後1個字符, 返回1個新字符串

 

10.

public String subString(int beginIndex, int endIndex)

截取從第beginIndex個字符開始, 第endIndex個字符, 返回1個新字符串, 是上面方法的重載

 

11.

public static String valueOf(...)

注意這個是靜態方法. 能夠把其餘基本數據類型轉換成String對象

 

12. 

Integer.parseInt(String s)

這個是另1個類Integer 的敬愛函數, 能夠把字符串轉換成int類型.  會拋出異常..

相關文章
相關標籤/搜索