目錄
- 創建字串
- 方式1
- 方式2
- 方式3
- 三種方式的記憶體圖
- 方式1 方式2
- 方式3
- 總結
- 理解池的概念
- 回憶參考
- 字串判斷相等
- 判斷字串參考是否相等
- 代碼1
- 代碼2
- 代碼3
- 代碼4
- 總結
- 判斷字串內容是否相等
- 變數與變數進行比較
- 字串常量與變數進行比較
- 理解字串不可變
- 反射打破字串不可變
- 字符與字串
- 代碼示例1:獲取指定位置的字符
- 代碼示例2:將字符陣列所有內容變為字串進行輸出
- 代碼示例3: 將字符陣列部分內容變為字串進行輸出
- 代碼示例4: 字串與字符陣列的轉換
- 小練習:字串的逆置
- 位元組與字串
- 代碼示例1: 將整個位元組陣列轉變為字串
- 代碼示例2: 將部分位元組陣列內容變為字串
- 代碼示例3: 將字串以位元組陣列的方式回傳
- 代碼示例4:編碼轉換處理
- 情況1 當字串為英文
- 情況2 當字串為中文(分編碼格式不同的情況)
- 總結
- 字串常規操作
- 字串比較
- 區分大小寫比較
- 不區分大小寫比較
- 比較兩個字串大小關系(conpareTo方法)
- 代碼:觀察conpareTo比較
- 字串查找
- 代碼示例1:contains方法
- 代碼示例2:indexOf(String str)方法
- 代碼示例3:indexOf(String str,int fromIndex)方法
- 代碼示例4:lastIndexOf(String str)方法
- 代碼示例5:lastIndexOf(String str,int fromIndex)方法
- 代碼示例6:startsWith(String prefix)方法
- 代碼示例7:startsWith(String prefix,int toffset)方法
- 代碼示例8:endsWith方法
- 字串替換處理
- 代碼示例1:replaceAll方法
- 代碼示例2:replaceFirst方法
- 代碼示例3::replace方法
- 字串拆分(用的非常多,需要多注意)
- 代碼示例1:split(String regex)方法
- 代碼示例2:split(String regex,int limit)方法
- 代碼示例3:特殊情況
- 情況1:拆分ip地址這類(單個分隔符)
- 情況2:多個分隔符的拆分(使用連字符“|”)
- 字串截取
- 代碼示例1:substring(int beginIndex)方法
- 代碼示例2:substring(int beginIndex,int endIndex)
- 其他操作方法
- trim方法
- toUpperCase方法
- toLowerCase方法
- intern方法(前面已經講過)
- concat方法(不經常用,不做過多贅述)
- length方法
- isEmpty方法
- StringBu?er 和 StringBuilder
- 代碼示例
- String與StringBuffer(StringBuilder)的轉換
- 總結
- String與StringBuffer,StringBuilder的區別(面試題)
- StringBuffer與StringBuilder區別(面試題)
- String與StringBuilder區別(面試題)
- 小結
創建字串
創建字串一共有三種方式:
方式1
String str1= "abc";
System.out.println(str1);
//輸出結果
abc
方式2
String str2= new String("abc");
System.out.println(str2);
//輸出結果
abc
方式3
char[] value={'a','b','c'};
String str=new String(value);
System.out.println(str);
//輸出結果
abc
三種方式的記憶體圖
方式1 方式2

在這里我們首先介紹一下字串常量池的概念:
String類的設計使用了共享設計模式
在JVM底層實際上會自動維護一個物件池,這個物件池稱為字串常量池
對于方式1中的賦值形式來說,因為是直接賦值,所以賦值的內容將自動保存到字串常量池當中,此時abc存入了字串常量池,并將abc得地址0x999賦給了str1這個參考
對于方式2的賦值形式來說,如果字串常量池當中有abc,就直接將abc的地址0x999賦給value陣列,然后再將String類在堆上實體化的物件的地址0x888賦給str2參考,如果字串常量池當中沒有abc,那么就把新開辟的字串物件abc存入常量池當中以供下次使用,然后把存進去的abc的地址賦給value陣列,然后再將String類在堆上實體化的物件的地址0x888賦給str2參考,至于這里為什么出現了value陣列,需要仔細剖析:
- 首先我們進行了String這個類的物件的實體化操作,所以一定會在堆上開辟記憶體.
- 此時再來看
String類的有參建構式中引數為字串的情況

可以看到我們將original的值賦給了value,再來看String類中value這個成員變數到底是什么把?

可以看到是私有的且被final所修飾的char型別的value陣列
注意:被final所修飾的成員變數此時在String類中并沒有進行初始化,所以需要在構造方法中進行初始化,所以這也是為什么this.value=original.val出現的原因.因為value這個陣列此時并沒有直接初始化,所以通過構造方法進行 傳參從而對value這個陣列進行初始化.
方式3
方式3的賦值方式的記憶體圖如下:

我們來分析下為什么是這樣畫的:
首先value是一個區域變數,所以先在堆疊上開辟記憶體,同時在堆上開辟記憶體存盤’a’,‘b’,‘c’,‘d’,'e’這五個元素,接下來再繼續在堆疊上開辟記憶體,存盤str3這個區域變數.同時在堆疊上開辟記憶體存盤String這個類的實體化物件.
接下來我們來看String這個類的有參建構式中引數為陣列時的原始碼的情況:

我們會發現其使用了copyof方法拷貝了一個新的陣列,而新拷貝的陣列其實就是我們的value陣列的復制品.相當于是將拷貝后的陣列賦值給了String類中所定義的value陣列.
所以就如圖中所畫的一樣,此時新拷貝的陣列的地址為0x888,將這個地址賦值給String類中的成員變數value陣列,然后再將String類在堆上的實體化物件的地址0x999賦值給我們的str3這個參考.
總結
這三種創建字串常量的方式,底層其實都與原始碼中被private和final所修飾的char型別的陣列有關
理解池的概念
“池” 是編程中的一種常見的, 重要的提升效率的方式, 我們會在未來的學習中遇到各種 “記憶體池”, “執行緒池”, “資料庫連接池” …
然而池這樣的概念不是計算機獨有, 也是來自于生活中. 舉個栗子:
現實生活中有一種女神, 稱為 “綠茶”, 在和高富帥談著物件的同時, 還可能和別的屌絲搞曖昧. 這時候這個屌絲被稱為 “備胎”. 那么為啥要有備胎? 因為一旦和高富帥分手了, 就可以立刻找備胎接盤, 這樣 效率比較高.
如果這個女神, 同時在和很多個屌絲搞曖昧, 那么這些備胎就稱為 備胎池.
回憶參考
我們曾經在講陣列的時候就提到了參考的概念.
參考類似于 C 語言中的指標, 只是在堆疊上開辟了一小塊記憶體空間保存一個地址. 但是參考和指標又不太相同, 指標能進行各種數字運算(指標+1)之類的, 但是參考不能, 這是一種 “沒那么靈活” 的指標.
另外, 也可以把參考想象成一個標簽, “貼” 到一個物件上. 一個物件可以貼一個標簽, 也可以貼多個. 如果一個物件上面一個標簽都沒有, 那么這個物件就會被 JVM 當做垃圾物件回收掉.
Java 中陣列, String, 以及自定義的類都是參考型別.
由于 String 是參考型別, 因此對于以下代碼
String str1 = "Hello";
String str2 = str1;
記憶體圖如下所示:

此時兩個參考指向了同一個物件
那么有同學可能會說, 是不是修改 str1 , str2 也會隨之變化呢?下面來看一段代碼:
String str1 = "Hello";
String str2 = str1;
str1 = "hello";
System.out.println(str2);
System.out.println(str1);
事實上,這樣的代碼并不算 “修改” 字串, 而是讓 str1 這個參考指向了一個新的 String 物件.
記憶體圖如下所示:

因為字串是一種不可變物件(接下來會細講),它的內容不可變,Sting類的內部實作也是基于char[]來實作的,因為String類原始碼中定義char[]型別時是如下格式:

也就是說char[]型別的陣列被final所修飾時,其地址是不能被修改的,假如我們此時修改了Hello字串的首字母H改為小寫h,相當于產生了一個新的字串常量hello,那么在常量池上就相當于產生了一個新的物件,就要分配新的地址,就不可能在原來Hello的地址上將其改為hello了,所以就如上圖所示了
那么要想在原地址改為hello,需要用到反射,在下面字串不可變那一章節我們會介紹反射這個概念,
字串判斷相等
判斷字串參考是否相等
如果現在有兩個int型變數,判斷其相等可以使用 == 完成,
int x = 10 ;
int y = 10 ;
System.out.println(x == y);
// 執行結果
true
如果說現在在String類物件上使用 == ,就是判斷參考是否相等,來看下面幾段代碼,并判斷字串的參考是否相等
代碼1
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);
// 執行結果
true
代碼1記憶體布局:

我們發現, str1 和 str2 是指向同一個物件的. 此時如 “Hello” 這樣的字串常量是在 字串常量池 中.
如 “Hello” 這樣的字串字面值常量, 也是需要一定的記憶體空間來存盤的. 這樣的常量具有一個特點, 就是不需要修改(常量嘛). 所以如果代碼中有多個地方參考都需要使用 “Hello” 的話, 就直接參考到常量池的這個位置就行了, 而沒必要把 “Hello” 在記憶體中存盤兩次.也就是說我們每次在創建字串的時候便會看常量池當中到底有沒有當前所需要創建的字串,如果有就不用在常量池中再創建一次了,
代碼2
String str = "abc";
String str2 = new String("abc");
System.out.println(str1 == str2);
//輸出結果
false
代碼2 記憶體圖如下

我們會發現此時最終代碼結果為false.
原因如圖所示:str1與str2參考所指向的物件均不相同,所以其存盤的地址也是不相同的,那么最終的比較一定為false.
那么在這個地方假如我們想讓str1與str2這兩個參考所存盤的地址相同的話,此時便引入了一特殊的概念:即手工入池:利用intern()方法
首先來看代碼:
String str1 = "hello" ;
String str2 = new String("hello") ;
System.out.println(str1 == str2);
// 執行結果
False
---------------------------------------------------------------------
String str1 = "hello" ;
String str2 = new String("hello").intern() ;
System.out.println(str1 == str2);
// 執行結果
true
來看這份代碼的記憶體圖:

我們可以看到此時str1與str2參考的地址相同,這是為什么呢?
答:這便是intern()方法的功勞,intern被稱為手工入池處理,對于
String str2 = new String(“hello”).intern() ; 這段代碼來說,是檢查此時字串常量池當中是否有hello這個字串常量,如果有,便將常量池中的參考回傳給當前的參考,對于上述代碼來說,此時常量池中是有hello這個字串常量的,那么就將其在常量池當中的地址賦給參考str2,則str1與str2此時擁有相同的地址了,最終str1==str2結果為true.
代碼3
1.public static void main(String[] args) {
2. String str1 = "hello";
3. String str2 = "hel" + "lo";//字串常量在編譯時就已經完成了字串的拼接,所以此處等價于 String str2= "hello";
4. String str3 = new String("hel") + "lo";
5. String str4 = new String("hel") + new String("lo");
6.
7. //true
8. System.out.println(str1 == str2);
9. //false
10. System.out.println(str3 == str1);
11. //false
12. System.out.println(str1 == str4);
13. //false
14. System.out.println(str3 == str4);
15.}
首先來看str1與str2,str3的比較:

此時我們str2中我們會發現是兩個字串常量在相加,常量相加有一個特點就是其在編譯時期就已經確定了,所以此時如果兩個字串常量相加的話就等價于拼接后的字串,那么str1==str2最后的結果便為true
現在來看str3,此時是一個new String物件加一個字串常量,在記憶體中可以看到此時String物件中是是hel字串,在常量池中并沒有,則放入常量池中,然后字串常量lo也沒有,也放進去,則此時堆上的物件指向常量池當中的hel
我們的代碼為兩者的拼接,那么就會在堆上開辟一個新的記憶體去存盤兩者拼接后的新字串hello,然后將這個新拼接的物件的地址賦給我們的str3參考,很顯然這是跟str1完全不一樣的地址,所以最終str1==str3的值為false
下面是str1與str4的比較:

同樣我們可以看到是兩個不同的地址,所以最終結果為false
代碼4
1.public static void main(String[] args) {
2. String str1 = "hello";
3. String str2 = "world";
4. //st1是變數,變數在程式運行時才知道里面存盤的內容
5. String str3 = str1 + "world";
6. //兩個字串常量相加在編譯時期就已經確定了,所以等價為helloworld
7. String str4 = "hello" + "world";
8. String str5 = "helloworld";
9. String str6=str1+str2;
10. //false
11. System.out.println(str3 == str5);
12. //true
13. System.out.println(str4 == str5);
14. //false
15. System.out.println(str5==str6);
16. }
此時我們可以看到str3中是一個變數和常量相加,在這里要注意,str1是變數,變數是只有在運行時才知道里面存盤的是什么,而常量是編譯時期就已經確定了,所以str3==str5為false,
Str4中是兩個常量相加,而常量在編譯的時候就已經確定了,所以str4等價于str5,則str4==str5為true
Str6同樣為兩個變數相加,變數是只有在運行時才知道里面存盤的是什么,所以str5str6, str6str4都為false.
總結
String中使用==比較的時候比較的并不是其字串內容是否相等,而比較的是兩個參考型別地址是是否相同,也就是判斷這兩個參考是否指向了相同的物件,
判斷字串內容是否相等
如果要判斷字串的內容是否相等,此時就需要使用equals關鍵字
變數與變數進行比較
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
// System.out.println(str2.equals(str1)); // 或者這樣寫也行
// 執行結果
true
字串常量與變數進行比較
現在需要比較 str 和 “Hello” 兩個字串是否相等, 我們該如何來寫呢?
String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));
在上面的代碼中, 哪種方式更好呢?
我們更推薦使用 "方式二". 一旦 str 是 null, 方式一的代碼會拋出空指標例外, 而方式二不會.例如:
String str = null;
// 方式一
System.out.println(str.equals("Hello")); // 執行結果拋出 java.lang.NullPointerException 例外
// 方式二
System.out.println("Hello".equals(str)); // 執行結果 false
理解字串不可變
字串是一種不可變物件. 它的內容不可改變.
String 類的內部實作也是基于 char[] 來實作的, 但是這個char[]陣列是私有的且被final所修飾的陣列,String 類并沒有提供 set 方法之類的來修改這個char型別的字符陣列.
所以原則上來說字串是一種不可變的物件,每創造一個新的字串常量都要在字串常量池當中重新開辟記憶體存盤.
感受下形如這樣的代碼:
String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str);
// 執行結果
hello world!!!
形如 += 這樣的操作, 表面上好像是在原地址修改了字串, 其實不是. 記憶體變化如下:

+= 之后 str 列印的結果的確是變了, 但并不是在str參考第一次指向的物件“hello”的地址上原地發生拼接, 而是每次拼接完成后就要在常量池中開辟臨時記憶體去存盤新拼接的臨時變數.因為每個字串都是不可變的,相當于如果按照上述方法的話每次拼接完成后實際上都是一個新的物件,新的物件每次挨個存盤在常量池中,str參考最終指向最后一個臨時變數.
那么假如我們要拼接100次,例如下面的代碼,那么就會在堆記憶體中的字串常量池產生99個臨時變數,這種方法是不可取的,這樣的代碼以后盡量不要出現在專案當中
1.public static void main(String[] args) {
2. String str1 = "abc";
3. for(int i = 0;i <= 100;i++) {
4. //要創造99個臨時變數
5. str1 += i;
6. }
7. System.out.println(str1);
7.}
那么對于在回圈中進行拼接產生大量臨時變數,降低效率的情況,我們改怎樣做呢?
此時需要用到
StringBuffer和StringBuilder可以來處理在回圈程序中拼接的這樣一個程序(單執行緒使用StringBuilder,多執行緒使用StringBuffer),使用兩者共有的append方法進行拼接,append方法的拼接是不會產生臨時變數的,后續我們在String與StringBuilder的 區別一欄中會給出詳細解答,大家可以直接通過目錄進行跳轉,觀看解答.
到了這里,我們可以了解到,字串常量因為底部原始碼實作的問題,它是不可變的,每次所創建的新的字串原則上是不能在原字串上進行修改變動的,必須在堆記憶體上的字串常量池中創建新的記憶體來存盤變動的字串,但是java中的反射打破了這一規則,當然后續我們也會仔細去講解反射,現在我們就來大致了解下反射,以及如何通過反射來打破字串不可變這一規則:
反射打破字串不可變
還是來看之前的一段代碼:
之前我們想將str1的“Hello”改成"hello"怎么做的呢?
常見辦法:借助原字串, 創建新的字串
String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);
// 執行結果
hello
那么利用反射該怎么做呢?
使用 "反射" 這樣的操作可以破壞封裝, 訪問一個類內部的 private 成員
IDEA 中 ctrl + 左鍵 跳轉到 String 類的定義, 可以看到內部包含了一個 char[] , 保存了字串的內容.被private所修飾,但是此時String類中并沒有提供對這個陣列的set方法.

代碼如下:
public static void main(String[] args) {
String str1 = "abc";
//Class物件
Class c1 = String.class;
//getDeclaredField方法可能會拋出NoSuchFieldException例外,需要被捕獲
try {
// 獲取 String 類中的 value 欄位. 這個 value 和 String 原始碼中的 value 是匹配的.
Field field = c1.getDeclaredField("value");
// 將這個欄位的訪問屬性設為 true
field.setAccessible(true);
//get方法可能會拋出IllegalAccessException例外,需要捕獲.
try {
// 把 str1 中的 value 屬性獲取到.
char[] value = (char[]) field.get(str1);
//這塊列印下獲取到的value屬性發現是【a,b,c】
System.out.println(Arrays.toString(value));
//列印下修改前的str1的值,為abc
System.out.println(str1);
// 修改 value 的值
value[0]='G';
//列印修改后的str1的值,為Gbc
System.out.println(str1);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
字符與字串
字串內部包含一個字符陣列,String 可以和 char[] 相互轉換.

代碼示例1:獲取指定位置的字符
String str = "hello" ;
System.out.println(str.charAt(0)); // 下標從 0 開始
// 執行結果
h
System.out.println(str.charAt(10));
// 執行結果如果超出下標范圍,產生 StringIndexOutOfBoundsException 例外
代碼示例2:將字符陣列所有內容變為字串進行輸出
1.char[] value = {'a', 'b', 'c', 'd'};
2.String str = new String(value);
//輸出結果為abcd
3.System.out.println(str);
代碼示例3: 將字符陣列部分內容變為字串進行輸出
1.char[] val = {'a', 'b', 'c', 'd'};
2.String str1 = new String(value, 1, 3);
3.//輸出結果為bcd
4.System.out.println(str1);
offet為偏移量,是計算從哪個下標開始(下標從0開始奇數),例如為1就是從第二個陣列元素開始,count為往后要的個數
假如此時個數超過了陣列元素或者offset超過了陣列長度-1,那么會發生StringIndexOutOfBoundsException例外
代碼示例4: 字串與字符陣列的轉換
String str = "helloworld" ;
// 將字串變為字符陣列
char[] data = str.toCharArray() ;
for (int i = 0; i < data.length; i++) {
System.out.print(data[i]+" ");
}
小練習:字串的逆置
方法:先使用toCharArray方法將字串轉變為字符陣列,然后再將char型別陣列轉變為字串回傳(共有三種方法回傳).
public static String reverse(String string) {
//字串轉為陣列
char[] chars = string.toCharArray();
int i = 0;
int j = chars.length-1;
while (i < j) {
char tmp = chars[i];
chars[i] = chars[j];
chars[j] = tmp;
i++;
j--;
}
//陣列轉化為字串(三種方法)
//return new String(chars);
//return String.copyValueOf(chars);
return String.valueOf(chars);
}
位元組與字串
位元組常用于資料傳輸以及編碼轉換的處理之中,String 也能方便的和 byte[] 相互轉換.

代碼示例1: 將整個位元組陣列轉變為字串
注意將位元組陣列轉變為字串時,是按照unicode編碼進行轉換的,例如97這個數字在unicode表中對應a這個字母
1.byte[] bytes={97,98,99,100,101,102};
2.String str=new String(bytes);
3.//輸出結果為abcdef
4.System.out.println(str);
代碼示例2: 將部分位元組陣列內容變為字串
1.byte[] bytes1={97,98,99,100,101,102};
2.String str2=new String(bytes1,1,3);
3.//輸出結果為bcd
4.System.out.println(str2);
代碼示例3: 將字串以位元組陣列的方式回傳
1.String string="abcde";
2.byte[] bytes2=string.getBytes();
3.//輸出結果為[97, 98, 99, 100, 101]
4.System.out.println(Arrays.toString(bytes2));
代碼示例4:編碼轉換處理
情況1 當字串為英文
String str3 = "gaobo";
try {
byte[] bytes3 = str3.getBytes("gbk");
System.out.println(Arrays.toString(bytes3));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//輸出結果
[103, 97, 111, 98, 111]
當字串轉變為位元組陣列的時候,此時我們字串為英文,我們設定編碼格式不管是gbk或者是utf8,最終的輸出結果都是一樣的
情況2 當字串為中文(分編碼格式不同的情況)
String str3 = "高博";
try {
byte[] bytes3 = str3.getBytes("gbk");
System.out.println(Arrays.toString(bytes3));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//輸出結果
[-72, -33, -78, -87]
當字串轉變為位元組陣列的時候,此時我們字串為中文,我們設定編碼格式不同,最終的輸出結果都是不一樣的,例如針對上述代碼,gbk的輸出結果為[-72, -33, -78, -87].
String str3 = "高博";
try {
byte[] bytes3 = str3.getBytes("utf8");
System.out.println(Arrays.toString(bytes3));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//輸出結果
[-23, -85, -104, -27, -11, -102]
當編碼格式為utf8的時候,輸出結果為[-23, -85, -104, -27, -115, -102]
總結
那么何時使用 byte[], 何時使用 char[] 呢?
- byte[] 是把 String 按照一個位元組一個位元組的方式處理,
這種適合在網路傳輸, 資料存盤這樣的場景下使用. 更適合針對二進制資料來操作. - char[] 是把 String 按照一個字符一個字符的方式處理,
更適合針對文本資料來操作, 尤其是包含中文的時候.
回憶概念: 文本資料 vs 二進制資料
一個簡單粗暴的區分方式就是用記事本打開能不能看懂里面的內容.
如果看的懂, 就是文本資料(例如 .java 檔案), 如果看不懂, 就是二進制資料(例如 .class 檔案).
字串常規操作
字串比較
上面使用過String類提供的equals()方法,該方法本身是可以進行區分大小寫的相等判斷,除了這個方法之外,String 類還提供有如下的比較操作:

區分大小寫比較
String str1 = "hello" ;
String str2 = "Hello" ;
System.out.println(str1.equals(str2));
// 輸出結果
false
不區分大小寫比較
String str1 = "hello" ;
String str2 = "Hello" ;
System.out.println(str1.equalsIgnoreCase(str2));
// 輸出結果
true
比較兩個字串大小關系(conpareTo方法)
在String類中compareTo()方法是一個非常重要的方法,該方法回傳一個整型,該資料會根據大小關系回傳三類內容:
1.相等:回傳0.
2.小于:回傳內容小于0.
3.大于:回傳內容大于0,
代碼:觀察conpareTo比較
System.out.println("A".compareTo("a")); // -32
System.out.println("a".compareTo("A")); // 32
System.out.println("A".compareTo("A")); // 0
System.out.println("AB".compareTo("AC")); // -1
System.out.println("劉".compareTo("楊")); //-5456
compareTo()是一個可以區分大小關系的方法,是String方法里是一個非常重要的方法,
它的比較規律如下:
字串的比較大小規則:總結成三個字 “字典序” 相當于判定兩個字串在一本詞典的前面還是后面. 先比較第一個字符的大小(根據 unicode 的值來判定), 如果不分勝負, 就依次比較后面的內容例如AB和AC中,一開始先比較A和A,發現兩個相同則為0,繼續往下比,B和C在unicode表中對應的數字分別為66,67,可以看出來B比C要小,所以回傳負數,這個負數的數字為66-67=-1.
字串查找
從一個完整的字串之中可以判斷指定內容是否存在,對于查找方法有如下定義

代碼示例1:contains方法
String str = "helloworld" ;
System.out.println(str.contains("world")); // true
contains方法的判斷形式是從JDK1.5之后開始追加的,在JDK1.5以前要想實作與之類似的功能,就必須借助、indexOf()方法完成,
來看contains方法的原始碼:

底層其實還是index方法
代碼示例2:indexOf(String str)方法
String str = "helloworld" ;
System.out.println(str.indexOf("world")); // 結果為5,w開始的索引System.out.println(str.indexOf("bit")); // 結果為-1,沒有查到
if (str.indexOf("hello") != -1) {
System.out.println("可以查到指定字串!");
}
代碼示例3:indexOf(String str,int fromIndex)方法
fromIndex是從前往后確定的位置,例如5就是從前往后數下標為5的字母,意思就是從這個字母開始查找是否存在str這個字串,有回傳這個字串的第一個字母,沒有回傳-1.
String str = "helloworld" ;
//結果為5
System.out.println(str.indexOf("world",5));
//結果為-1
System.out.println(str.indexOf("world",6));
代碼示例4:lastIndexOf(String str)方法
lastIndexOf方法雖然是從后面開始往前數有沒有world這個單詞,如果有,回傳數字的時候還是回傳world這個單詞中w所在的下標,沒有回傳-1
String str = "helloworld" ;
//結果為5
System.out.println(str.lastIndexOf("world"));
代碼示例5:lastIndexOf(String str,int fromIndex)方法
String str = "ababcfacd" ;
//結果為2
System.out.println(str.lastIndexOf("ab",4));
此段代碼相當于從下標為4處的字母開始從后往前尋找是否存在ab,如果有,直接回傳2,沒有回傳-1.此時從c開始往前尋找ab,找到了以后回傳第一次出現ab中a字母的下標
代碼示例6:startsWith(String prefix)方法
String str = "ababcfacd" ;
//結果為true
System.out.println(str.startsWith("ab"));
代碼示例7:startsWith(String prefix,int toffset)方法
String str = "ababcfacd" ;
//結果為false
System.out.println(str.startsWith("ab",4));
代碼示例8:endsWith方法
String str = "ababcfacd" ;
//結果為false
System.out.println(str.endsWith("ab"));
字串替換處理
使用一個指定的新的字串替換掉已有的字串資料,可用的方法如下:

代碼示例1:replaceAll方法
String str = "helloworld" ;
//結果為he__owor_d
System.out.println(str.replaceAll("l", "_"));
代碼示例2:replaceFirst方法
String str = "helloworld" ;
//結果為he_loworld
System.out.println(str.replaceFirst("l", "_"));
代碼示例3::replace方法
String str = "helloworld" ;
//結果為he__owor_d
System.out.println(str.replace("l", "_"));
可以發現replace方法與replaceAll方法的效果一樣
來看下replace方法的原始碼吧:

可以看到replace方法的底層代碼中的引數是CharSequence型別,但是我們自己傳參的時候是string型別,這是為什么呢?
答:是因為發生了向上轉型,String類實作了CharSequence這個介面,如下圖所示:

注意事項:由于字串是不可變物件, 替換不修改當前字串, 而是產生一個新的字串
字串拆分(用的非常多,需要多注意)
可以將一個完整的字串按照指定的分隔符劃分為若干個子字串,
可用方法如下:

代碼示例1:split(String regex)方法
split方法回傳的是一個String型別的陣列,以下是多次拆分的代碼,以后會經常出現
String str = "username=zhangsan&password=123";
//以“&”這個符號進行切割,切割一次后為username=zhangsan password=123
String[] strings = str.split("&");
for (int i = 0; i < strings.length; i++) {
//繼續對第一次切割后的字串進行切割
String[] strings1 = strings[i].split("=");
for (int j = 0; j < strings1.length; j++) {
/*最終結果為:
username
zhangsan
password
123
*/
System.out.println(strings1[j]);
}
}
代碼示例2:split(String regex,int limit)方法
limit=1說明只分了一組
String str = "username=zhangsan&password=123";
String[] strings = str.split("&",1);
for (int i = 0; i < strings.length; i++) {
//輸出結果為username=zhangsan&password=123
System.out.println(strings[i]);
}
limit=2說明分為兩組
String str = "username=zhangsan&password=123";
String[] strings = str.split("&",2);
for (int i = 0; i < strings.length; i++) {
/*輸出結果為:
username=zhangsan
password=123
*/
System.out.println(strings[i]);
}
拆分是特別常用的操作. 一定要重點掌握. 另外有些特殊字符作為分割符可能無法正確切分, 需要加上轉義
代碼示例3:特殊情況
情況1:拆分ip地址這類(單個分隔符)
String str = "192.168.1.1" ;
String[] result = str.split("\\.") ;
for(String s: result) {
/*輸出結果為
192
168
1
1
*/
System.out.println(s);
}
像ip地址這種拆分的時候,如果是拿點號(.)進行拆分的話,需要進行轉義,第一次轉義是轉義點號,第二次轉義是保留轉義符號的作用,意思就是讓第一個\作為轉義符號來使用
情況2:多個分隔符的拆分(使用連字符“|”)
String str = "java-split#bit";
//用|作為連字符,將-和#這兩個分隔符進行拆分
String[] strings = str.split("-|#");
for (int i = 0; i < strings.length; i++) {
/*輸出結果為:
java
split
bit
*/
System.out.println(strings[i]);
}
注意事項:
字符"
|","*","+“都得加上轉義字符,前面加上”\"而如果是"",那么就得寫成"\\".
如果一個字串中
有多個分隔符,可以用"|"作為連字符.
字串截取
從一個完整的字串之中截取出部分內容,可用方法如下:

代碼示例1:substring(int beginIndex)方法
String str = "abcdefg";
String str1= str.substring(3);
//輸出結果為:defg
System.out.println(str1);
注意事項:
索引從0開始,3就是索引為3所對應的那個數字,也就是d
代碼示例2:substring(int beginIndex,int endIndex)
String str = "abcdefg";
String str1= str.substring(0,5);
//輸出結果為:abcde
System.out.println(str1);
從0下標開始截取,截取到4號下標,不包含5號下標所對應的字符
注意事項:
注意前閉后開區間的寫法, substring(0, 5) 表示包含 0 號下標的字符, 不包含 5 號下標
其他操作方法

trim方法
trim 會去掉字串開頭和結尾的空白字符(
空格, 換行, 制表符等).
public class string {
public static void main(String[] args) {
String str = " abc de fg ";
//輸出結果為: abc de fg
System.out.println(str);
String str1=str.trim();
//輸出結果為:abc de fg
System.out.println(str1);
}
}
toUpperCase方法
public class string {
public static void main(String[] args) {
String str = "abc";
String str1=str.toUpperCase();
//輸出結果為:ABC
System.out.println(str1);
}
}
toLowerCase方法
public class string {
public static void main(String[] args) {
String str = "ABabc";
String str1=str.toLowerCase();
//輸出結果為:ababc
System.out.println(str1);
}
}
注意事項:toUpperCase方法和toLowerCase方法這兩個方法只轉換字母,不轉換中文字符,
intern方法(前面已經講過)
concat方法(不經常用,不做過多贅述)
length方法
public class string {
public static void main(String[] args) {
String str = "abc";
int length=str.length();
//輸出結果為:3
System.out.println(length);
}
}
注意事項:陣列長度使用陣列名稱.length屬性,而String中使用的是length()方法
isEmpty方法
public class string {
public static void main(String[] args) {
//定義一個空字串
String str = "";
boolean boolean1=str.isEmpty();
//輸出結果為:true
System.out.println(boolean1);
}
}
注意有
兩種方式可以表示空字串
第一種方式:String str="",這種方式代表指向的字串物件什么都沒有.
第二種方式:String str=null,這種方式代表不指向任何物件
StringBu?er 和 StringBuilder
首先來回顧下String類的特點:
任何的字串常量都是String物件,而且String的常量一旦宣告不可改變,如果改變物件內容,改變的是其參考的指向而已,
通常來講String的操作比較簡單,但是由于String的不可更改特性,為了方便字串的修改,提供StringBu?er和StringBuilder類,
StringBu?er 和 StringBuilder 大部分功能是相同的,我們主要介紹 StringBu?er
在String中使用"+"來進行字串連接,但是這個操作在StringBu?er類中需要更改為append()方法
使用了append方法進行拼接后,新產生的字串在字串常量池就不再生成記憶體了,就直接在原記憶體上的字串進行拼接
代碼示例
public class string {
public static void main(String[] args) {
//append方法的字串拼接是在原字串基礎上進行拼接的,并不是生成新的字串
StringBuffer stringBuffer=new StringBuffer("abcd");
//輸出結果為abcdefg
System.out.println(stringBuffer.append("efg"));
}
}
為什么append方法會在原字串上進行拼接呢?
來看append方法的原始碼:

最后回傳的是當前物件的參考,所以就是在原字串上進行拼接.
String與StringBuffer(StringBuilder)的轉換
String和StringBu?er(StringBuilder)類不能直接轉換,如果要想互相轉換,可以采用如下原則:
String變為StringBu?er(StringBuilder):利用StringBu?er(StringBuilder)的構造方法或append()方法 (常用)
StringBu?er(StringBuilder)變為String:呼叫toString()方法,(常用)
總結
String與StringBuffer,StringBuilder的區別(面試題)
-
StringBuffer,StringBuilder包含了一些String沒有的方法 比如reverse方法
-
StringBuffer,StringBuilder是可變的,String是不可變的,String的每次拼接,都會產生新的物件,
StringBuffer,StringBuilder每次的拼接都回傳的是this,說明是在原字串上進行拼接的.
StringBuffer與StringBuilder區別(面試題)
先來看StringBuffer,StringBuilder的append方法:
StringBuffer類中的append方法是被synchronized修飾的,
這個關鍵字可以保證執行緒的安全.


總結:
StringBuilder和String出現在單執行緒情況下
StringBuffer因為有synchronized關鍵字,所以一般出現多執行緒情況下,
一般來說非多執行緒的情況下最好使用StringBuilder,原因是 synchronized關鍵字涉及到鎖的問題,每次的開鎖關鎖都要消耗資源
StringBu?er采用同步處理,屬于執行緒安全操作;而StringBuilder未采用同步處理,屬于執行緒不安全操作
String與StringBuilder區別(面試題)
既然都運用于單執行緒的情況下,那么這兩者到底有什么區別呢?
1:String的拼接”+“會被優化 優化為StringBuilder中的append方法,來看分析:
我們將下面這段代碼進行編譯:
public class string {
public static void main(String[] args) {
String str="abc";
str=str+"de";
System.out.println(str);
}
}
編譯后得到的如下圖所示:

可以看到底部進行了優化,那么對于上述代碼來說其實底層在運行的時候的代碼如下所示:(代碼的執行方式跟圖中是一樣的)
public class string {
public static void main(String[] args) {
String str="abc";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str);
stringBuilder.append("hello");
String str1 = stringBuilder.toString();
System.out.println(str1);
}
}
所以說對于String類的”+“的拼接,底層雖然是StringBuilder類的append方法的優化,但是其仍然會產生大量的臨時空間
再來看之前遺留的一個問題:
public static void main(String[] args) {
String str = "abc";
for(int i = 0;i < 10;i++) {
str += i;
}
System.out.println(str);
}
上述代碼之前我們說因為由于”+“號的拼接,導致會產生大量的臨時變數,此時我們來使用StringBuilder的append方法來進行優化:
public class string {
public static void main(String[] args) {
String str = "abc";
//在回圈外定義StringBuilder物件
StringBuilder sb = new StringBuilder();
sb.append(str);
for (int i = 0; i < 10; i++) {
//回圈內部使用append方法
str = sb.append(i).toString();
}
System.out.println(str);
}
}
2:在回圈當中 不可以使用String直接進行拼接 這樣會產生大量的臨時物件
包括優化之后的StringBuilder物件,所以每次定義StringBuilder物件的時候在回圈外進行定義,然后在回圈內部使用append方法.
小結
字串操作是我們以后作業中非常常用的操作. 使用起來都非常簡單方便, 一定要使用熟練. 指的注意的點:
1.字串的比較, ==, equals, compareTo 之間的區別.
2.了解字串常量池, 體會 “池” 的思想.
3.理解字串不可變
4.split 的應用場景
5.StringBu?er 和 StringBuilder 的功能.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/296895.html
標籤:其他
上一篇:Vue.js框架基礎
