詳解Java中String類的各種用法

一、創建字符串

創建字符串的方式有三種:

// 方式一
String str = "Hello Bit";
// 方式二
String str2 = new String("Hello Bit");
// 方式三
char[] array = {'a', 'b', 'c'};
String str3 = new String(array);

我們對第一和第二種創建字符串的方法都已經非常熟悉瞭,那至於為什麼第三種能夠傳入一個字符數組變為字符串,我們可以按住ctrl鍵點入傳入字符數組的String當中看其原碼,我們能夠發現此時是利用的方法是數組的拷貝,將字符數組的所有字符改為字符串形式。

在這裡插入圖片描述

二、字符、字節與字符串的轉換

1.字符與字符串的轉換

因為字符串等同於是一個個字符的集合,因此要想字符轉為字符串則要調用String的構造方法並傳入一個字符數組。例如:

char[] val = {'a','b','c'};
String str = new String(val);
System.out.println(str);

當然,我們也可以選擇字符數組從哪個下標開始到哪個下標結束的字符轉換為字符串的形式。

例如:

char[] val = {'a','b','c','d','e'};
String str1 = new String(val,0,3);
System.out.println(str1);
//打印結果為abc
System.out.println("=========");
String str2 = new String(val,1,3);
System.out.println(str2);
//打印結果為bcd

因為字符串是字符的集合,因此可以字符串可以轉換為一個字符或者一個字符數組。括號內的比如0、1是偏移量,偏移量是從0開始的,因此從偏移量為0的位置處往後取3個字符構成一個字符串。

如果字符串要轉換為單個字符,代碼如下:

String str = "abc";
System.out.println(str.charAt(1));
//打印結果為b

如果字符串要轉換為字符數組,代碼如下:

char[] val = str.toCharArray();
System.out.println(Arrays.toString(val));
//打印結果:[a, b, c]

2.字節與字符串的轉換

Java中的將字節轉為字符串需要將字節數組轉為字符串。

字節數組轉換為字符串:

byte[] bytes = {97,98,99,100};
String str1 = new String(bytes,0,3);
System.out.println(str1);
//打印結果為abc
System.out.println("========");
String str2 = new String(bytes,1,3);
System.out.println(str2);
//打印結果為bcd

字符串轉換為字節數組:

String str1 = "abc";
byte[] bytes1 = str1.getBytes();
System.out.println(Arrays.toString(bytes1));
//打印結果為:[97, 98, 99]

三、字符串的比較

有許多初學者會認為,“ == ”與equals比較的方式是相同的。其實有很大的區別。
對於兩個字符串用“ == ”比較,比較的是變量的引用。而String的equals方法比較的是兩個字符串的內容。但此時又有個疑問:為什麼每個定義字符串常量的是一個引用呢?這樣就牽扯到瞭字符串常量池。

1.字符串常量池

對於“池”這個概念,可能大傢還是比較陌生的。比如數據連接池、線程池等等。那這些池的作用的幹嘛的呢?是用來提高存儲效率的。顧名思義字符串常量池是用來存儲字符串常量的。字符串常量池中規定隻要有瞭一個字符串常量就不再存儲相同的字符串瞭。從JDK1.8開始字符串常量池是在堆裡的。它本質上是一個哈希表(StringTable)是一個數組。存儲字符串常量是,會根據一個映射關系進行存儲,這個映射關系需要設計一個哈希函數。(因為字符串常量池是有關於JVM的,需要看其原碼才能真正瞭解字符串常量池是如何操作的,此處不深究其原理也不會影響我們判斷引用是否相同)。

字符串常量池中當存儲一個字符串常量時會在根據哈希函數計算的某一個位置處產生一個結點,結點是由哈希值、String結點的地址、存儲該數組位置處的下一個結點的地址組成的(這在JVM的原碼中才能真正瞭解)。而每一個String結點是由字符型數組value與哈希值hash(默認為0)構成的 (下圖所示)。點入String看其原碼時就能夠會發現這兩個變量。此時觀察到value數組被final修飾則說明該數組裡的字符是不能夠被改變的,這就是字符串是一個常量的原因,並且該字符串會轉換為字符形式存放在字符數組當中。

在這裡插入圖片描述

先來舉一個比較簡單的例子來理解字符串常量池的內存佈局。
代碼如下:
代碼一:

String str1 = "hello";
String str2 = "hello";
System.out.println(str1==str2);
//打印結果為true

內存佈局如下:

在這裡插入圖片描述

代碼二:

String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1==str2);
//打印結果為false

在這裡插入圖片描述

手動入池:
我們根據上圖知道瞭代碼二中的運行結果是false的,因為str2指向的是new String產生的String對象,而不是存儲“hello”的String對象的地址。如果寫為下面這個代碼,結果會是如何呢?

String str1 = "hello";
String str2 = new String("hello").intern();
System.out.println(str1==str2);
//運行結果為true

為什麼最後的結果為true呢?此時調用瞭String類當中的intern方法,稱為手動入池,它能夠將str2的指向不再指向new出來的String對象,而是指向瞭字符串常量池當中已經存儲有“hello”字符數組的String對象。

代碼三:

String str1 = "hello";
String str2 = "he"+"llo";
System.out.println(str1==str2);
//打印結果為true

此代碼有關字符串的拼接。其實“he”與“llo”在編譯時期就已經編譯為“hello”瞭。如果要看編譯時期str2是什麼字符串,則此時我們先點擊Build選項,點入Build Project選項則進行編譯(圖1)。可以在該類文件的路徑(含有.class文件)底下(圖2+圖3),按住shift鍵加右鍵點擊powershell窗口,輸入反編譯指令javap -c 類名則能看到編譯時str2是否是已經拼接好的hello。

圖1:

在這裡插入圖片描述

圖2:在該類的窗口處點擊鼠標右鍵

在這裡插入圖片描述

圖三:退回上一個文件夾,點入out->prodection->字節碼文件所在的該文件夾名->按住shift點擊鼠標右鍵點入powershell窗口->輸入javap -c 類名

在這裡插入圖片描述

代碼四:

String str1 = "11";
String str2 = new String("1")+new String("1");
System.out.println(str1==str2);
//運行結果為false

字符串的拼接會產生一個StringBuffer的類型,通過StringBuffer調用toString方法也轉變為String類,此時拼接完後字符串“11”存儲在value中,但是不會存儲到字符串常量池當中。

通過反編譯我們看到的確拼接產生StringBuffer,並且StringBuffer調用toString方法產生一個String類的對象存儲“11”。由圖1、圖2可以完全瞭解。

圖1:

在這裡插入圖片描述

圖2:

在這裡插入圖片描述

圖3:

在這裡插入圖片描述

2.字符串內容比較

對於字符串比較,我們不能直接用“==”,而有三種方法能夠對字符串有不同的比較方式。

比較字符串內容:直接調用String類的equals方法,將字符串放入括號當中比較。
比較字符串內容(不分字母大小寫):調用String類的equalsIgnoreCase方法。

String str1 = "hello" ; 
String str2 = "Hello" ; 
System.out.println(str1.equals(str2)); // false 
System.out.println(str1.equalsIgnoreCase(str2)); // true

比較字符串中的大小:調用String類當中的compareTo方法。本來String類當中是沒有compareTo方法,隻不過String類實現瞭Comparable接口,並且重寫瞭compareTo方法。

在這裡插入圖片描述

它是一個字符一個字符進行比較的。如果str1大於str2則返回str1該字符減去str2該字符的值。例如:

代碼1:

String str1 = "abc";
String str2 = "bcd";
System.out.println(str1.compareTo(str2));
//運行結果為:-1

因為b的ASCII碼值比a的ASCII碼值大1,則直接返回-1。(如果是字符不相同則返回它們的ASCII碼差值)

代碼2:

String str1 = "bcdef";
String str2 = "bcd";
System.out.println(str1.compareTo(str2));
//運行結果為2

因為在str2比較結束前與str1的字符值是相同的。因此最後的結果是str1的長度減去str2的長度。

下面是String類的compareTo方法的實現。

在這裡插入圖片描述

四、字符串查找

1.判斷一個子串是否存在於主串中:調用String類的contains方法,返回值為boolean。

String str = "abbabcacc";
boolean flg = str.contains("abc");
System.out.println(flg);
//打印結果為true

2.從頭開始查找一個子串,並返回第一個子串開始的索引位置,如果沒有,則返回-1。也可以傳入一個索引,代表是從哪個索引位置開始尋找,調用String類中的indexOf方法。

String str = "abbabcacc";
int index = str.indexOf("abc");
System.out.println(index);
//打印結果為3

3.從尾處開始尋找,查看主串中有無傳入的子串,若有則返回索引值,沒有則返回-1。調用String類的lastIndexOf,並且也可以傳入索引代表從哪個索引值從尾處尋找到頭處。調用String類的lastIndexOf
代碼1:

String str = "abbabcacc";
int index = str.lastIndexOf("ac");
System.out.println(index);
//打印結果為6

當我們要找的子串剛好被“切斷”時,它仍然會取到後面的字符返回子串開始的索引值,但是後面的字符的索引值不能取到。

代碼2:

String str = "abbabcacc";
int index = str.lastIndexOf("ac",6);
System.out.println(index);
//打印結果為6

4.判斷一個字符串是否以指定子串開頭,調用String類中的startsWith方法。也可以傳入索引值說明從指定位置開始判斷是否以指定子串開頭。

String str = "abbabcacc";
boolean flg = str.startsWith("abb");
System.out.println(flg);
//打印結果為true
String str = "abbabcacc";
boolean flg = str.startsWith("abb",3);
System.out.println(flg);
//打印結果為false

5.判斷一個字符串是否以指定子串結尾。調用String類當中的endsWith方法。

String str = "abbabcacc";
boolean flg = str.endsWith("acc");
System.out.println(flg);
//打印結果為true

五、字符串替換

1.替換字符串中的所有的指定內容。調用String類當中的repalceAll方法。

String str = "helloworld" ; 
System.out.println(str.replaceAll("l", "_"));
//打印結果為he__owor_d

也可以選擇替換字符串中的首個內容。調用String類中的repalceFirst方法。

System.out.println(str.replaceFirst("l", "_"));
//打印結果為he_loworld

由於字符串是不可變對象, 替換不修改當前字符串, 而是產生一個新的字符串。

六、字符串拆分

指定字符串在主串的基礎上能分為幾個組就等於分為幾個String類數組。因此可以通過foreach循環來遍歷拆分後的數組的內容。調用String類的split方法。

String str = "hello world hello bit" ; 
String[] result = str.split(" ") ; // 按照空格拆分
for(String s: result) { 
 System.out.println(s); 
}
//打印結果為

hello
world
hello
bit

split方法還能夠傳入一個limit參數,代表拆分後最多分為幾個數組。如果拆分後數組的個數小於這個limit值則按原來拆分的數組的個數拆分,否則數組的個數不能夠超過limit值

String str = "hello world hello bit" ; 
String[] result = str.split(" ",2) ; 
for(String s: result) { 
 System.out.println(s); 
}
//打印結果為

hello
world hello bit

當然,對於字符串的拆分可以嵌套拆分,即先拆分為兩部分,再根據另一個字符串再拆分。

String str = "name=zhangsan&age=18";
String[] strings = str.split("&");
for (String s:strings) {
 String[] ss = s.split("=");
 for (String s1:ss) {
   System.out.println(s1);
 }
}
//打印結果為

name
zhangsan
age
18

對於字符串的拆分還有幾種特殊情況,當遇到需要拆分的為轉義字符時,傳入指定的字符串則需要傳多兩個斜杠。例如:

String str = "192.168.1.1";
String[] strings = str.split("\\.");
for (String s:strings) {
     System.out.println(s);
}
//打印結果為

192
168
1
1

因此需要註意的是:字符”|”,”*”,”+“都得加上轉義字符,前面加上”\\”。

對於字符串的拆分還可以根據多個指定的字符串進行拆分,指定的字符串之間用‘|’分隔。

String str = "Java30 12&21#hello";
String[] strings = str.split(" |&|#");
for (String s:strings) {
     System.out.println(s);
}
//打印結果為

Java30
12
21
hello

七、字符串截取

對於一個字符串的截取,傳入一個索引值代表是從哪個索引開始截取。傳入兩個索引值則代表截取的范圍。調用String類中的substring方法。例如:

String str = "helloworld" ;
System.out.println(str.substring(5));
System.out.println(str.substring(0, 5));
//打印結果為

world
hello

註意:

  • 索引從0開始
  • 註意前閉後開區間的寫法, substring(0, 5) 表示包含 0 號下標的字符, 不包含 5 號下標

對於以上字符串操作的方法,我們可以查看其原碼能夠更好地瞭解該方法是如何進行操作的。

八、String類中其它的常用方法

1.String類的trim方法。這個方法是用來去掉字符串中左右兩邊空格,而字符串中間的空格是不會去掉的。
代碼:

String str = "  abc  def  ";
String s = str.trim();
System.out.println(s);
//打印結果為

abc def

2.String類中的toUpperCase和toLowerCase方法。toUpperCase是用來將字符串中的小寫字母轉變為大寫字母,而不是字母的不進行處理。toLowerCase方法是用來將字符串中的大寫字母轉變為寫寫字母,而不是字母的也不進行處理。

String str = " hello%$$%@#$%world 哈哈哈 " ; 
System.out.println(str.toUpperCase()); 
System.out.println(str.toLowerCase());
//打印結果為:

HELLO%$$%@#$%WORLD 哈哈哈
hello%$$%@#$%world 哈哈哈

3.String類中的concat方法。這個方法是用來連接字符串的,相當於字符串中的拼接,但是連接後的字符串不會入到字符串常量池當中。這裡不再演示。

4.String類中的length方法。它是用來求字符串長度的,跟數組不一樣,數組中的length是數組的屬性,而String中的length是一個方法。
代碼:

String str = "abcd";
System.out.println(str.length());
//打印結果為4

5.String類中的isEmpty方法。是用來判斷字符串是否為空的。
代碼:

System.out.println("hello".isEmpty());//false
System.out.println("".isEmpty());//true
System.out.println(new String().isEmpty());//true

九、StringBuffer 和 StringBuilder

對上面String字符串常量池有瞭瞭解後,我們知道瞭String是常量,是不可變的。當拼接時,Java會在編譯期間將String類的對象拼接優化為StringBuffer的拼接(不會產生新對象),因此Java中有StringBuffer和StringBuilder中處理字符串,並且它們拼接時不會產生新的對象,而是在原來的字符串基礎上拼接。後面我們再將StringBuilder和StringBuffer的區別。
StringBuilder中有一個append方法可以將字符串在原來的基礎上拼接。例如當我們有這樣的代碼時:

String str = "abc";
for (int i = 0; i < 10; i++) {
     str+=i;
}
System.out.println(str);
//打印結果:

abc0123456789

會在常量池中產生很多的臨時變量,例abc0會在字符串常量池中產生,abc01又會在字符串常量池中產生等等。如果我們用到StringBuilder的append方法時,可以寫為(兩種寫法最後的結果是相同的,隻是StringBuilder處理時不會在字符串常量池中產生臨時變量):

StringBuilder stringBuilder = new StringBuilder("abc");
for (int i = 0; i < 10; i++) {
     stringBuilder.append(i);
}
System.out.println(stringBuilder);
//打印結果:

abc0123456789

打印時為什麼能夠打印StringBuilde類型是因為StringBuilder中重寫瞭父類的toString方法,它能夠把StringBuilder類型轉變為String類型進行打印。

append方法也可以連著使用。代碼如下:

public static void main(String[] args) { 
StringBuffer sb = new StringBuffer(); 
sb.append("Hello").append("World"); 
fun(sb); 
System.out.println(sb); 
}
//打印結果為

HelloWorld

因此:
String變為StringBuffer:利用StringBuffer的構造方法或append()方法。
StringBuffer變為String:調用toString()方法。

1.StringBuilder與StringBuffer的區別

StringBuilder與StringBuffer中的方法都是大致相同的。它們的主要區別就是StringBuilder主要是用於單線程的,而StringBuffer主要是用於多線程的。我們點入StringBuffer類當中按住ctrl+7選擇append方法看到如圖所示的synchronized英文,則代表是多線程使用的,而StringBuilder類中沒有。

在這裡插入圖片描述

結論:

  • String的內容不可修改,StringBuffer與StringBuilder的內容可以修改.
  • StringBuffer與StringBuilder大部分功能是相似的
  • StringBuffer采用同步處理,屬於線程安全操作;而StringBuilder未采用同步處理,屬於線程不安全操作

2.StringBuilder與StringBuffer常用的方法

StringBuilder與StringBuffer常用的方法一般String類當中都是沒有的,例如:append方法、delete方法、reserve方法、insert方法等。

reverse方法:

StringBuffer sb = new StringBuffer("helloworld"); 
System.out.println(sb.reverse());
//打印結果為

dlrowolleh

delete方法:

StringBuffer sb = new StringBuffer("helloworld"); 
System.out.println(sb.delete(5, 10));
//打印結果為

hello

insert方法:

StringBuffer sb = new StringBuffer("helloworld"); 
System.out.println(sb.delete(5, 10).insert(0, "你好"));
//打印結果為:

你好hello

十、對字符串引用的理解

許多人認為,如果傳一個引用到一個函數中指向瞭另一個對象,就能夠解決“所有問題”,其實不然,下面這個例子能夠說明問題。
代碼:

    public static void func(String str1,char[] chars) {
    str1="hello";
    chars[0]='g';
    }
    public static void main(String[] args) {
        String str = "abcd";
        char[] chars = {'h','e','l','l','o'};
        func(str,chars);
        System.out.println(str);
        System.out.println(Arrays.toString(chars));
    }
    //打印結果為 

abcd
[g, e, l, l, o]

結果為什麼str沒有指向hello而chars而又還是改變的是原來那個hello的基礎上變為gello呢?在我們學瞭字符串常量池後,瞭解到瞭字符串常量池隻能存儲一個內容相同的字符串常量。因此,傳入func函數當中str1是一個形參,是str1指向瞭新的對象“hello”,但是main函數中的str指向的還是abcd。而chars是真真正正地在原來數組的基礎上0下標的字符改為瞭‘g’,最後打印的結果為[g, e, l, l, o]是沒有任何問題的。圖解如下:

在這裡插入圖片描述

到此這篇關於詳解Java中String類的各種用法的文章就介紹到這瞭,更多相關Java String類內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: