前言
字串型別廣泛應用在 Java 編程當中,在之前 【Java】資料型別和運算子 這一小節里面有簡單介紹過一點 String 型別,但是那不過是 String 型別的相關內容的九牛一毛
在本小節當中將向大家深刻剖析 String 類,了解其創建方法,記憶體存盤,各項基本操作以及深刻理解 StringBuffer 和 StringBuilder 類從而對字串做出更多的改變,
文章目錄
- 前言
- 一、創建字串
- 二、字串常量池
- 三、字串比較相等
- 四、實體分析(:red_circle:)
- 五、字串與字符 & 字串與位元組
- 5.1 字串與字符
- 5.2 字串與位元組
- 六、String類常見的操作(:red_circle:)
- 6.1 字串比較
- 6.2 字串查找
- 6.3 字串替換
- 6.4 字串拆分
- 6.5 字串截取
- 6.6 其余常用操作
- 七、StringBuffer 類 & StringBuilder類(:red_circle:)
一、創建字串
常見的創建字串的方式:
//方法一
String str1 = "Hello world";
//方法二
String str2 = new String("Hello");
//方法三
char[] array = {'W','o','r','l','d'};
String str3 = new String(array);
記憶體布局:

💬代碼解釋:
- String 是
參考型別,其變數是參考變數,存放的是堆里面的一塊地址- “Hello” 是
字串字面值常量,也是 String 型別- 方法一是
直接賦值方法,直接讓 str1 指向堆里面 “Hello world” 這一字串- 方法二是 String 的
構造方法,先實體化一個物件,讓該物件指向堆里面“Hello“ 這一字串- 方法三是將
一個字符陣列轉化為一個字串,向實體化的 String 類里傳一個字符陣列 array ,呼叫其中這樣型別的構造方法,該構造方法中將 array 陣列的內容拷貝了一份,讓 String 類里的value 陣列接收了新拷貝的內容- 一般來說,方法一使用的更加的多
另外,在C語言的同學知道字串是以’\0’結尾的,但是在 Java 中并沒有這樣的說法,
二、字串常量池
📑代碼示例:
String str1 = "hello" ;
String str2 = "hello" ;
System.out.println(str1 == str2);
💬代碼解釋:
將該代碼放入main 函式中,會發現結果為 true
- String 型別是參考型別,因此使用 == 并不是在比較字串的內容是否相等,而是在比較 str1 和str2 這兩個參考
是否指向同一個物件- 結果為 true 說明,str1 和 str2 指向的 “Hello” 字串是同一個 ,并沒有在堆中開辟兩塊不一樣的空間分別存放兩個 “Hello”
實際上,為了避免每次都創建相同的字串物件,多余的進行記憶體的分配,JVM內部對字串物件的創建做了一定的優化,在堆中有一塊區域用來存盤字串,該區域就是字串常量池,當直接賦值時,字串內容若在字串常量池中,就直接進行參考,若字串常量池中沒有,則將字串內容自動保存到字串常量池中
三、字串比較相等
📑代碼示例:
//例1
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);
//例2
String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str3 == str4);
💬代碼解釋:
將該代碼放入main 函式中,會發現例1結果為 true,例2結果為 false
在字串常量池這一小節中有說到 == 是在比較 str1 和str2 這兩個參考是否指向同一個物件,那么,,,
記憶體布局:

- str1 和 str2 采取的是直接賦值法,因此兩者指向的是同一個物件,比較之后結果為 true
- str3 和 str4 采取的是 new String 的方法,相當于在堆中由開辟了兩塊新的空間來存盤 “Hello” 的內容,比較后結果為 false
若真的想要對字串的內容進行比較,就需要采用 equals 方法
📑代碼示例:
String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str3.equals(str4));
//實體:
//System.out.println("Hello".equals(str3));
💬代碼解釋:
將該代碼放入main 函式中,結果為 true
注意:
- 在使用該方法時一定要
確保 str3 不是 null,如果是就會報空指標例外,但是 str4 可以是 null- 用實體的方法來比較 “Hello” 和 str3 的值的內容是否相等是比較好的,因為 “Hello” 也是 String 物件,也可以使用 equals 方法,且不存在會報錯的風險
四、實體分析(🔴)
實體一:
📑代碼示例:
public static void main(String[] args) {
//例一
String str1 = "Hello world";
String str2 = "Hello" + " world";
System.out.println(str1 == str2)
//例二
String str3 = "Hello world";
String str4 = "Hello";
String str5 = str4 + " world";
System.out.println(str3 == str5);
}
💬代碼解釋:
例一的結果為 true,例二的結果為 false
- 在例一中,“Hello” 和 " world" 都是字串常量,在編譯的時候會進行優化,
自動拼接,因此實際上,str2 指向的物件的內容就是 “Hello world”- 在例二中,str4 是一個變數,在編譯的時候并不知道它的值,運行時才會知曉,因此 str5 指向的是 “Hello” 和 “ world” 拼接后產生的新的物件
記憶體布局:

實體二:
📑代碼示例:
public static void main(String[] args) {
//例一
String str1 = "Hello world";
String str2 = "Hello" + new String(" world");
System.out.println(str1 == str2);
// 例二
String str3 = "Hello world";
String str4 = new String("Hello") +new String(" world");
System.out.println(str3 == str4);
}
💬代碼解釋:
例一和例二的結果都是 false
記憶體布局:
例一:

例二:

實體三:
📑代碼示例:
public static void main(String[] args) {
//例一
String str1 = new String("Hello") + new String(" world");
str1.intern();
String str2 = "Hello world";
System.out.println(str1 == str2);
//例二
String str3 = new String("Hello") + new String(" world");
String str4 = "Hello world";
str3.intern();
System.out.println(str3 == str4);
}
💬代碼解釋:
例一的結果為 true,例二的結果為 false
inter()的作用是手動將字串入池(字串常量池)
判斷 str1 目前指向的物件在常量池中有沒有,發現沒有,就將這個物件放入到常量池中
判斷 str3 目前指向的物件在常量池中有沒有,發現有,就無需有所行動
記憶體布局:
例一:

例二:

inter方法的優點:
如果用構造方法創建字串,每次都會在堆上開辟兩塊記憶體空間,傳入的字串引數是一個匿名物件,使用一次后將不會被使用,將會成為垃圾空間,還有就是同一個字串可能會被存盤很多次,浪費空間,使用 inter 方法,將池里沒有的字串手動匯入池中,就可以避免這一麻煩,
實體四:
📑代碼示例:
public static void main(String[] args) {
String str1 = "Hello";
String str2 = str1;
str1 = "world";
System.out.println(str1 == str2);
}
💬代碼解釋:
結果為 false
str1 和 str2 都是參考型別,str2 指向了 str1 指向的物件,但是卻沒有辦法通過 str1 去改變 str2 指向的物件,因此就算這兩個參考指向同一片空間,也是沒有辦法通過其中一個修改另一個的內容

實體五:
眾所周知,字串常量是沒有辦法進行修改的,若是想要將字串 “Hello” 改成 “hello”,應當怎么操作呢?
📑代碼示例1:
public static void main(String[] args) {
String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);
}
💬代碼解釋:
結果為 hello
- 該方法實際上就是借助原來的字串,創建新的字串,并沒有將原來的 “Hello” 真正的改成 “hello”
- substring(1) 表示從 str 字串偏移量為1開始提取字串
📑代碼示例2:
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
String str = "Hello";
Class c = String.class;
//獲取String類中的value欄位
Field field = c.getDeclaredField("value");
//權限修改了
field.setAccessible(true);
//把str中的value屬性獲取到.
char[] vals = (char[]) field.get(str);
//修改value的值
vals[0] = 'h';
//hello
System.out.println(str);
}
💬代碼解釋:
結果為 hello
- 這里用到了
反射的操作(對于該操作以后會進行仔細的講解),該操作指的就是在程式運行程序中,獲取或修改某個物件的詳細資訊,會破壞封裝
實體六:
📑代碼示例:
public static void main(String[] args) {
String str = "hello" ;
for(int x = 0; x < 1000; x++) {
str+= x ;
}
System.out.println(str);
}
💬代碼解釋:
結果為一字串,“hello01234…998999”
這樣的代碼不應該出現在開發當中,每次回圈都會產生新的物件,效率低下

五、字串與字符 & 字串與位元組
5.1 字串與字符
字串內部包含一個字符陣列 value,String 可以和 char[] 相互轉換
📑代碼示例:
public static void main(String[] args) {
//例一
char[] value = {'h','e','l','l','o'};
String str1 = new String(value,1,4);
System.out.println(str1);
//例二
String str2 = "hello";
char ch = str2.charAt(1);
System.out.println(ch);
//例三
String str3 = "hello";
char[] chars = str3.toCharArray();
System.out.println(Arrays.toString(chars));
}
🏸 代碼結果:

💬代碼解釋:
例一
public String(char[] value,int offset,int count)是 String 類的一個構造方法,表示從偏移量為1的位置開始取4個字符來構造String物件,將取出的部分變成字串,切記偏移量和取的字符個數不可超出陣列,否則會陣列越界例外
例二
public char charAt(int index)是取得指定索引位置的字符,切記索引的大小不可超過字串中字符的個數減一,否則會陣列越界例外
例三
public char[] toCharArray()是將字串以字符陣列的方式進行存盤
📑代碼示例:
判斷給定的字串其是否全部由數字所組成
public class TestDemo {
public static boolean isNumber (String str ) {
if(str == null) return false;//str不指向任何物件
if(str.length() == 0) return false;//空串
char[] chars = str.toCharArray();
for (char ch :chars) {
if(ch < '0' || ch >'9') {
return false;
}
}
return true;
}
public static void main(String[] args) {
String str = "";
if(isNumber(str)) {
System.out.println("全都是數字字符!");
}else {
System.out.println("不全都是數字字符!");
}
}
}
注意:
一定要考慮完善,空串以及參考變數不指向任何物件多需要考慮到
5.2 字串與位元組
位元組常用于資料傳輸以及編碼轉換的處理之中,String 也能方便的和 byte[] 相互轉換
📑代碼示例:
public class TestDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
//例一
byte[] bytes = {97,98,99,100};
String str = new String(bytes);
System.out.println(str);
//例二
String str3 = new String(bytes,1,3);
System.out.println(str3);
//例三
String str4 = "abcd";
byte[] bytes5 = str4.getBytes();
System.out.println(Arrays.toString(bytes5));
//例四
String str5 = "你好";
byte[] bytes3 = str5.getBytes("utf-8");
System.out.println(Arrays.toString(bytes3));
byte[] bytes4 = str5.getBytes("gbk");
System.out.println(Arrays.toString(bytes4));
}
}
🏸 代碼結果:

💬代碼解釋:
例一
public String(byte[ ] bytes)是將位元組陣列中的每一個元素變成對應的字符,字符組成字串
例二
public String(byte[ ] bytes,int offset,int length)是 String 類的一個構造方法,表示從偏移量為1的位置開始取3個位元組陣列的元素來構造String物件,將取出的部分變成字串,切記偏移量和取的字符個數不可超出陣列,否則會陣列越界例外
例三
pulic byte[ ] getBytes( )是將字串以位元組陣列的形式回傳
例四
public byte[ ] getBytes(String charsetName) throws UnsupportedEncodingException是編碼轉換處理,以不同的位元組碼去獲取字串,將其轉換為 byte 陣列(一個漢字 utf-8 占3個位元組,gbk 占2個位元組)
📑示例:

使用該方法 String 型別上會出現一道
橫線,點進去發現有@Deprecated這一注解,說明該方法已經被棄用
六、String類常見的操作(🔴)
6.1 字串比較
📑代碼示例:
public class TestDemo {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "aBc";
//例一(比較相等)
System.out.println(str1.equals(str2));
//例二(比較相等)
System.out.println(str1.equalsIgnoreCase(str2));
//例三(比較大小)
System.out.println(str1.compareTo(str2));
}
}
🏸 代碼結果:

💬代碼解釋:
例一
public boolean equals(Object anObject)在之前的字串比較相等中有介紹過,作用就是比較兩個字串是否相等(區分大小寫)
例二
public boolean equalsIgnoreCase(String anotherString)作用也是比較兩個字串是否相等(不區分大小寫)
例三
public int compareTo(String anotherString)作用為比較兩個字串的大小關系
str1 大于 str2 回傳正數,等于 str2 回傳 0,小于 str2 回傳負數,
在兩比較的字串從后往后找的程序中,找到的第一個不相同的字符,這倆字符的大小關系就是整個字串的大小關系,例如 str1 中的字符 b 比 str2 中的字符 B 大32,就回傳正數32
如果兩個字串前面的字符都相等,但長度有所不一樣,就回傳長度的差值
6.2 字串查找
📑代碼示例:
public class TestDemo {
public static void main(String[] args) {
String str1 = "ccabbabbcc";
//例一(是否存在)
System.out.println(str1.contains("abb"));
//例二(從頭查找指定字串)
System.out.println(str1.indexOf("abb"));
//例三(從指定位置查找指定字串)
System.out.println(str1.indexOf("abb", 3));
//例四(從后向前查找指定字串)
System.out.println(str1.lastIndexOf("abb"));
//例五(從指定位置從后向前查找指定字串)
System.out.println(str1.lastIndexOf("abb", 5));
//例六(是否以指定字串開頭)
System.out.println(str1.startsWith("cca"));
//例七(從指定位置判斷是否以指定字串開頭)
System.out.println(str1.startsWith("cab", 1));
//例八(是否以指定字串結尾)
System.out.println(str1.endsWith("bcc"));
}
}
🏸 代碼結果:

💬代碼解釋:
例一
public boolean contains(CharSequence s)作用為判斷一個子字串是否存在,當且僅當字串包含指定的 char 值序列時才回傳 true,String 類有實作了 CharSequence 這個介面,因此可以直接傳 String 型別的引數
例二
public int indexOf(String str)作用為從頭開始查找指定的字串的位置,回傳指定字符第一次出現的字串內的索引,查找不到回傳-1
例三
public int indexOf(String str, int fromIndex)作用為從指定的索引開始,回傳指定字符第一次出現的字串內的索引,查找不到回傳-1
例四
public int lastIndexOf(String str)作用為回傳指定子字串最后一次出現的字串中的索引,查找不到回傳-1
例五
public int lastIndexOf(String str, int fromIndex)作用為從指定的索引開始從后向前搜索,回傳指定子字串最后一次出現的字串中的索引,查找不到回傳-1
例六
public boolean startsWith(String prefix)作用為測驗此字串是否以指定的前綴開頭
例七
public boolean startsWith(String prefix, int toffset)作用為測驗在指定索引處開始的此字串的子字串是否以指定的前綴開頭
例八
public boolean endsWith(String suffix)作用為測驗此字串是否以指定的后綴結尾
6.3 字串替換
📑代碼示例:
public class TestDemo {
public static void main(String[] args) {
String str1 = "cab cab cab";
//例一
System.out.println(str1.replace('a', 'p'));
//例二
System.out.println(str1.replaceAll("ab", "qq"));
//例三
System.out.println(str1.replaceFirst("ab", "pp"));
}
}
🏸 代碼結果:

💬代碼解釋:
例一
public String replace(char oldChar, char newChar)作用為將字串中出現的字符(oldChar)替換為新的字符(newChar)
例二
public String replaceAll(String regex, String replacement)作用為將字串中出現的子字串(regex)替換為新的字串(replacement)
例三
public String replaceFirst(String regex, String replacement)作用為將字串中出現的第一個子字串(regex)替換為新的字串(replacement)
字串是沒有辦法進行改變的,此處并未修改字串 str1,而是產生了新的字串
6.4 字串拆分
📑代碼示例:
public class TestDemo {
public static void main(String[] args) {
String str1 = "aaa bbb ccc";
String[] result1 = str1.split(" ");//例一
for (String s:result1) {
System.out.println(s);
}
System.out.println("===========最多分兩組===========");
String[] result2 = str1.split(" ",2);//例二
for (String s:result2) {
System.out.println(s);
}
}
}
🏸 代碼結果:

💬代碼解釋:
例一
public String[] split(String regex)作用為以字串(regex)為分隔符,將字串全部拆分
例二
public String[] split(String regex, int limit)作用為以字串(regex)為分隔符,將字串拆分,拆分的極限為 limit
📑代碼示例:
多次拆分
public class TestDemo {
public static void main(String[] args) {
String str1 = "name=Java&price=18";
String[] strings = str1.split("&");//第一次拆分
for (int i = 0; i < strings.length; i++) {
String[] strings1 = strings[i].split("=");//第二次拆分
System.out.println(Arrays.toString(strings1));
}
}
}
🏸 代碼結果:

💬代碼解釋:
每一次的拆分實際上都形成了一個陣列,多次拆分這樣的代碼會經常用到
注意:
字符
"|","*",",","+","."都是一些特殊的字符,需要進行轉義,例如"\\+"(第一條 \ 將第二條 \ 轉義為真正的斜杠,第二條 \ 將 +轉義為真正的 + )如果是“”(空字串),就需要寫成“\\”
若想要字串同時被多個分隔符進行分割,那么分隔符之間需要用
"|"將其分開,例如str.split(" |#|@");
6.5 字串截取
📑代碼示例:
public class TestDemo {
public static void main(String[] args) {
String str1 = "Hello World!";
//例一
System.out.println(str1.substring(1));
//例二
System.out.println(str1.substring(2, 7));
}
}
🏸 代碼結果:

💬代碼解釋:
例一
public String substring(int beginIndex)作用為從指定索引截取到結尾
例二
public String substring(int beginIndex, int endIndex)作用為截取部分內容,從索引 beginIndex 截取到 endIndex ,前閉后開
6.6 其余常用操作
📑代碼示例:
public class TestDemo {
public static void main(String[] args) {
String str1 = " Hello 中國! ";
//例一
System.out.println(str1.trim());
//例二
System.out.println(str1.toUpperCase());
//例三
System.out.println(str1.toLowerCase());
//例四
System.out.println(str1.concat("你好!"));
//例五
System.out.println(str1.length());
//例六
System.out.println(str1.isEmpty());
}
}
🏸 代碼結果:

💬代碼解釋:
例一
public String trim()作用為洗掉字串任何前導和尾隨的空白字符,比如空格、換行符、制表符等(保留中間的),回傳一個新的字串
例二
public String toUpperCase()作用為字串轉大寫,只轉字母
例三
public String toLowerCase()作用為字串轉小寫,只轉字母
例四
public String concat(String str)作用為字串拼接作用,相當于’+’
例五
public int length()作用為取得字串的長度,注意這是一個方法,要和陣列的 length 區分開來,那只是陣列的一個屬性
例六
public boolean isEmpty()作用為判斷字串是否為空,注意是指字串的長度為0,不是指 null
實際上,上面的很多方法都有很多的多載,在此不將各種多載的方法一一過一遍,舉一反三
七、StringBuffer 類 & StringBuilder類(🔴)
眾所周知,String 型別的字串常量是不可以進行改變的,StringBuffer 和 StringBuilder 類的出現就可以實作對字串的修改,這兩個類大多數的功能都是一樣的,接下來講主要以 StringBuffer 類為范例,來說說 String 類和這兩個類的區別,
區別一:
雖然這兩者都能夠表示字串,但是方式有所不同,String 類創建字串的方式有文章開頭介紹的那三種,但是 StringBuffer 類是不能夠進行直接賦值,可以通過其構造方法進行創建字串,
📑代碼示例:
public class TestDemo {
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer("Hello");
System.out.println(stringBuffer);
}
}
區別二:
在 Sting 類里面想要實作拼接作用,用的是 ‘+’ 號,在 StringBuffer 類中采取 append() 方法進行拼接,想要一直拼接就一直點 append() 方法即可,
更加重要的是 StringBuffer 類的這個物件在 append() 的時候是可以進行改變的,所進行的拼接實際上是在原來的物件中進行拼接,沒有產生新的物件,拼接完后,回傳的是當前物件,也并沒有對字串常量池中的字串有做過什么修改,
📑代碼示例:
public class TestDemo {
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer("Hello");
stringBuffer.append(" world").append("!!!");
System.out.println(stringBuffer);
}
}
👁?🗨 查看底層:

眾所周知,在拼接 String 類的字串時,原理上是會產生新的物件,但是編譯器在編譯的時候進行了優化
📑代碼示例:
public static void main(String[] args) {
String str = "hello" ;
for(int x = 0; x < 10; x++) {
str += x ;
}
System.out.println(str);
}
👁?🗨 查看底層:

如果按照反匯編顯示的那樣進行代碼實作,就應該是這樣的,,,
📑代碼示例:
public class TestDemo {
public static void main(String[] args) {
String str = "hello" ;
for(int x = 0; x < 10; x++) {
StringBuilder stringBuilder = new StringBuilder();
str = stringBuilder.append(str).append(x).toString();
}
System.out.println(str);
}
}
然而,該代碼在實作的時候仍然有不完善的地方,每次進入回圈都需要實體化一個新的物件,因此可以這樣升級代碼,使之只需要 new 一個 StringBuilder,,,
📑升級代碼示例:
public class TestDemo {
public static void main(String[] args) {
String str = "hello" ;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str);
for(int x = 0; x < 10; x++) {
stringBuilder.append(x);
}
str = stringBuilder.toString();
System.out.println(str);
}
}
區別三:
StringBuffer 類有很多 String 類沒有的方法,比如之前提到的 append() 方法
📑代碼示例:
public class TestDemo {
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer("lufituaeb olleH");
System.out.println(stringBuffer);
//例一
System.out.println(stringBuffer.reverse());
//例二
System.out.println(stringBuffer.delete(2, 5));
//例三
System.out.println(stringBuffer.insert(12,"!!!"));
}
}
🏸 代碼結果:

💬代碼解釋:
例一
public synchronized StringBuffer reverse()作用為反轉字串
例二
public synchronized StringBuffer delete(int start, int end)作用為洗掉指定范圍的資料,[start,end)
例三
public synchronized StringBuffer insert(int offset, String str)作用為在指定索引處插入指定的資料
String 類和 StringBuffer 類之間的轉換:
public class TestDemo {
public static void main(String[] args) {
//String——>StringBuffer
StringBuffer stringBuffer = new StringBuffer("Hello");
String str = stringBuffer.toString();
System.out.println(str);
//StringBuffer——>String
//方法一
String str2 = "world";
StringBuffer stringBuffer1 = new StringBuffer(str2);
System.out.println(stringBuffer1);
//方法二
StringBuffer stringBuffer2 = new StringBuffer();
stringBuffer2.append(str2);
System.out.println(stringBuffer2);
}
}
StringBuffer 類和 StringBuilder 類的區別:
StringBuffer 類被
synchronized關鍵字修飾,而 StringBuilder 類 就沒有
StringBuffer采用同步處理,屬于執行緒安全操作,適合多執行緒情況;而StringBuilder未采用同步處理,屬于執行緒不安全操作,適合單執行緒情況
關于執行緒會在之后的博客中進行詳細講解,,,
完!
sa
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/305672.html
標籤:java
