前言
本文介紹了字符串的存儲常量池及字符串常使用的方法,介紹了StringBuffer 和 StringBuilder,有什么地方寫的不好歡迎指正評論,謝謝!
1. 定義字符串
首先C語言是沒有String類型的,我們來看一下Java當中String的構造 String 的方式。
String定義:
// 方式一 String str = "Hello Bit"; // 方式二 String str2 = new String("Hello Bit"); // 方式三 char[] array = {'a', 'b', 'c'}; //把數組變成字符串 String str3 = new String(array);
結果:
我們可以去幫助文檔去深入了解一下Sting Java幫助手冊。
首先我們可以看見這個是在lang包底下:所以不需要導包
被final修飾不可以繼承(它繼承object類,所有的類都繼承object類)
所有的類要產生,就要有構造方法我們來看一下:(不一定學這么多,我們挑重點來講)
以上就是定義的方式:我們以后都是直接賦值不要在使用new一個的方式。
我們來看一下 “方式三” 的一個底層實現圖:
char[] array = {'a', 'b', 'c'}; //把數組變成字符串 String str3 = new String(array);
我們去看一下String類里面有什么:(有很多字段方法)但是我們只看values[ ]
string類里面有個value
我們在來看一下String的方法:(可以看見這個value拷貝了一份會產生新的一個副本)
以上就是方式三在底層完成的一個操作.
2.字符串比較相等
下面代碼輸出的是true 還是 false??
結果:
為什么呢?不是一樣的嗎?
- 因為str1是引用類型存放的是地址
- st2也是引用類型存放的也是地址
- 所以不一樣(比較的是還是值一不一樣)但是值不是字符串值,而是引用值。
那么想比較字符串的內容一不一樣怎么比較呢?
使用:equals
結果:
equals 使用注意事項
現在需要比較 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
注意事項: “Hello” 這樣的字面值常量, 本質上也是一個 String 對象, 完全可以使用 equals 等 String 對象的方法.
3. 字符串常量池( 多圖解析 )
字符串常量池:存儲的就是字符串。
在JVM底層實際上會自動維護一個對象池(字符串常量池)
- 如果現在采用了直接賦值的模式進行String類的對象實例化操作,那么該實例化對象(字符串內容)將自動保存到這個對象池之中.
- 如果下次繼續使用直接賦值的模式聲明String類對象,此時對象池之中如若有指定內容,將直接進行引用。
- 如若沒有,則開辟新的字符串對象而后將其保存在對象池之中以供下次使用
大概圖示:( 在JVM 并沒有劃分區域說xxx區域就是字符串常量池 )
Stringpool 其實底層就是一個哈希表(不深入可百度)
理解 “池” (pool)
“池” 是編程中的一種常見的, 重要的提升效率的方式, 我們會在未來的學習中遇到各種 “內存池”, “線程池”, “數據
庫連接池” …
然而池這樣的概念不是計算機獨有, 也是來自于生活中. 舉個栗子:
現實生活中有一種女神, 稱為 “綠茶”, 在和高富帥談著對象的同時, 還可能和別的潘扛汴用. 這時候這個潘勘
稱為 “備胎”. 那么為啥要有備胎? 因為一旦和高富帥分手了, 就可以立刻找備胎接盤, 這樣 效率比較高.
如果這個女神, 同時在和很多個潘扛汴用, 那么這些備胎就稱為 備胎池.
第一個列子解析:
還是來看一下下面的代碼在內存中的存放操作:
如下圖:根據上面的字符串常量池的介紹,我們知道字符串引用起來的,會放在常量池里面:
那么str2的字符串內容在哪里呢??
-我們都知道字符串存放在常量池里面,但是現在str也是hello,那么new string會先去常量池里面找有沒有一樣的,有一樣的就引用常量池里面的,就不需要在放進去新建一個:
最終str1是指向常量池里面的,str2是指向堆里面的,所以是false。
會發現其實常量池幫我們節省了空間,不需要同樣東西存兩份,這個就是常量池的意義。
第二個列子解析:
看下面代碼思考是否相等?
結果:
解析:這2個在java當中都是字符串常量,編譯器編譯的時候會自動被拼接,變成linbo
內存圖:
不相信?我們來編譯一下看看:(第二個字符串已經變成 linbo)
常量在編譯的時候已經被運算了。
變量:就是在編譯的時候 不知道里面的值,在運行的時候 才知道里面的值
在來列子:被final修飾變成常量
結果
反編譯:已經變成30
第三個列子解析:
思考下面代碼得到什么:?
結果:
解析:str3指向的其實是lin 和 bo的一個拼接,在堆中產生新的對象,所以是false;
第四個列子解析:
思考下面結果:
結果:
解析:可以看見str2(str2要拼接所以指向新對象)指向的是堆里面的對象,而str1是指向常量池的對象,所以是false
第五個列子解析:
思考下面的代碼:
結果:
解析:str2依然是指向拼接的新對象,而str1是指向常量池所以false;
第六個列子解析:
inter():手動將字符串入池。
思考下面的代碼:
結果:
解析:inter方法就是拿到對象里面的值去哈希表里面找,如果已經有了,就不放進去了 ,所以是false;
第七個列子解析:
思考下面代碼:
結果:
解析:s3.inter 放進了(放的是地址)常量池,然后s4的11 指向 s3的11 ,所以就是true
第八個列子解析:
思考下面代碼:
結果:
解析:s3變成11,但是在此之前s4的11是沒有的,所以放進常量池,然后說s3.inter,看看常量池有沒有,常量池是有的,所以沒有放進去所以s4是指向常量池的,s3是指向堆中的對象,所以不一樣。
以上就是所有的字符串內容相不相等的練習,相信看完這些你會對字符串的比較有個全新的認知。
字符串傳參注意事項:
不是傳引用就可以改變原來的值,看下面代碼:
結果:
解析:形參的改變并不影響實參,只是改變了指向
面試題:請解釋String類中兩種對象實例化的區別
直接賦值:只會開辟一塊堆內存空間,并且該字符串對象可以自動保存在對象池中以供下次使用。構造方法:會開辟兩塊堆內存空間,不會自動保存在對象池中,可以使用intern()方法手工入池。
綜上, 我們一般采取直接賦值的方式創建 String 對象
4. 理解字符串不可變
字符串是一種不可變對象. 它的內容不可改變.
String 類的內部實現也是基于 char[] 來實現的, 但是 String 類并沒有提供 set 方法之類的來修改內部的字符數組
String str = "hello" ; str = str + " world" ; str += "!!!" ; System.out.println(str); // 執行結果 hello world!!!
圖文解析:先hello 和world拼接 變成hello world ,然后在和!!!拼接變成hell world,
產生了5個對象,為什么這樣呢?就是因為那些字符串不可以改變(不可以直接在后面加)
那么如果實在需要修改字符串, 例如, 現有字符串 str = “Hello” , 想改成 str = “hello” , 該怎么辦?
a) 常見辦法: 借助原字符串, 創建新的字符串
String str = "Hello"; str = "h" + str.substring(1); System.out.println(str); // 執行結果 hello
b) 特殊辦法(選學): 使用 “反射” 這樣的操作可以破壞封裝, 訪問一個類內部的 private
IDEA 中 ctrl + 左鍵 跳轉到 String 類的定義, 可以看到內部包含了一個 char[] , 保存了字符串的內容.
String str = "Hello"; // 獲取 String 類中的 value 字段. 這個 value 和 String 源碼中的 value 是匹配的. Field valueField = String.class.getDeclaredField("value"); // 將這個字段的訪問屬性設為 true valueField.setAccessible(true); // 把 str 中的 value 屬性獲取到. char[] value = (char[]) valueField.get(str); // 修改 value 的值 value[0] = 'h'; System.out.println(str); // 執行結果 hello
關于反射:(暫時不深入)
反射是面向對象編程的一種重要特性, 有些編程語言也稱為 “自省”.
指的是程序運行過程中, 獲取/修改某個對象的詳細信息(類型信息, 屬性信息等), 相當于讓一個對象更好的 “認清自己” .
為什么 String 要不可變?(不可變對象的好處是什么?) (選學)
- 方便實現字符串對象池. 如果 String 可變, 那么對象池就需要考慮何時深拷貝字符串的問題了.
- 不可變對象是線程安全的.
- 不可變對象更方便緩存 hash code, 作為 key 時可以更高效的保存到 HashMap 中
注意事項: 如下代碼不應該在你的開發中出, 會產生大量的臨時對象, 效率比較低.
String str = "hello" ; for(int x = 0; x < 1000; x++) { str += x ; } System.out.println(str);
后面講stringbuffer 和 stringbuilder會講
5.字符, 字節與字符串
代碼:str: 第二個參數是偏移量,第三個參數是第幾個:
意思:從偏移量為1 ,取兩個 字符來構造String對象 。所以是bc
a是0,b是1
public static void main(String[] args) { char[] value = {'a','b','c','d','e'}; String str = new String(value,1,2); System.out.println(str); //bc }
代碼:從1位置處取一個
結果:h是0 e是1;
代碼:將字符串已字符數組方式進行存儲
結果:
代碼示例: 給定字符串一個字符串, 判斷其是否全部由數字所組成.
思路: 將字符串變為字符數組而后判斷每一位字符是否是" 0 “~”‘9'"之間的內容,如果是則為數字.
public static void main(String[] args) { String str = "1a23456" ; System.out.println(isNumber(str)? "字符串由數字所組成!" : "字符串中有非數字成員!"); } public static boolean isNumber(String str) { if(str==null)return false; if(str.lenth()==0)return false; char[] data = str.toCharArray() ; //轉變數組 for (int i = 0; i < data.length; i++) { if (data[i]<'0' || data[i]>'9') { return false ; } } return true ; }
字節與字符串
代碼:把字節(ASCII),轉換為合適的字符輸出
結果:
代碼:String上面有橫線是干嘛的??
點進去看看: 有這個代表被棄用
代碼:把字符串變成字節數組 返回出來
結果:
代碼:
結果:
但是漢字有問題:
結果:
utf-8:
結果:
不經常使用這個
小結
那么何時使用 byte[], 何時使用 char[] 呢?
byte[] 是把 String 按照一個字節一個字節的方式處理, 這種適合在網絡傳輸, 數據存儲這樣的場景下使用. 更適合針對二進制數據來操作char[] 是吧 String 按照一個字符一個字符的方式處理, 更適合針對文本數據來操作, 尤其是包含中文的時候.
6. 字符串常見操作
6.1 字符串比較
代碼: 比較引用的對象是否相等
public static void main(String[] args) { String str = "hello"; String str2 = new String("hello"); System.out.println(str.equals(str2)); //結果true }
代碼:忽略大小寫的情況
public static void main(String[] args) { String str = "hello"; String str2 = new String("Hello"); System.out.println(str.equalsIgnoreCase(str2)); //結果 true }
代碼:比較大小返回數字 str>str2 正數 ,str==str2 0 ,< 返回負數(比較的是ASCII碼)
public static void main(String[] args) { String str = "hello"; String str2 = new String("Hello"); System.out.println(str.compareTo(str2)); //返回 32 }
如果第一個一樣(從前往后比較),比較第二個字符,有一個字符大小不同,那么代表整個字符串大小
6.2 字符串查找
代碼:判斷字符串是否存在
public static void main(String[] args) { String str = "abcdefg"; boolean flag = str.contains(str); System.out.println(flag); //true }
列子2:
public static void main(String[] args) { String str = "abcdefg"; boolean flag = str.contains("cdef"); System.out.println(flag); //true }
代碼:查找cdef 在 str里面有沒有 ,看第一個字符的位置(沒有返回-1)
public static void main(String[] args) { String str = "abcdefg"; System.out.println(str.indexOf("cdef")); // 返回下標2 }
有點類似kmp算法
代碼:從指定位置找
public static void main(String[] args) { String str = "abcdefg"; System.out.println(str.indexOf("bcdef",0)); // 1 }
注意:位置的范圍
代碼:從后往前找
public static void main(String[] args) { String str = "abcdefg"; System.out.println(str.lastIndexOf("bcdef")); // 1 }
代碼:從指定的位置往前找
public static void main(String[] args) { String str = "abcdefbcdefg"; System.out.println(str.lastIndexOf("bcdef",10)); // 6 }
返回的是后面往前面的第一個bcdef
代碼:判斷是否以xx 開始的
public static void main(String[] args) { String str = "abcdefbcdefg"; System.out.println(str.startsWith("a")); // true }
public static void main(String[] args) { String str = "abcdefbcdefg"; System.out.println(str.startsWith("acdf")); // false }
代碼:從指定位置開始 從b開始判斷是不是a開頭
public static void main(String[] args) { String str = "abcdefbcdefg"; System.out.println(str.startsWith("a",1)); // false }
代碼:true
public static void main(String[] args) { String str = "abcdefbcdefg"; System.out.println(str.endsWith("efg")); // true }
代碼:false
public static void main(String[] args) { String str = "abcdefbcdefg"; System.out.println(str.endsWith("efgc")); // false }
6.3 字符串替換
代碼:所以的a 變成z
public static void main(String[] args) { String str = "abcdefbcdaaa"; System.out.println(str.replaceAll("a","z")); // zbcdefbcdzzz }
代碼:替換了第一個
public static void main(String[] args) { String str = "abcdefbcdaaa"; System.out.println(str.replaceFirst("a","z")); // zbcdefbcdaaa }
注意事項: 由于字符串是不可變對象, 替換不修改當前字符串, 而是產生一個新的字符串
6.4 字符串拆分
代碼:
public static void main(String[] args) { String str = "ab abc abcd"; String[] strings = str.split(" "); //以空格分割 for (String s:strings) { System.out.println(s); } }
結果:
ab
abc
abcd
代碼二:
public static void main(String[] args) { String str = "ab abc abcd"; String[] strings = str.split(" ",2); //以空格分割 最多分2組 for (String s:strings) { System.out.println(s); } }
//結果
ab
abc abcd
拆分是特別常用的操作. 一定要重點掌握. 另外有些特殊字符作為分割符可能無法正確切分, 需要加上轉義
代碼示例: 拆分IP地址
String str = "192.168.1.1" ; String[] result = str.split("\\.") ; for(String s: result) { System.out.println(s); }
注意事項:
字符"|","*","+“都得加上轉義字符,前面加上”".而如果是"",那么就得寫成"\".如果一個字符串中有多個分隔符,可以用"|"作為連字符.(代碼如下)
public static void main(String[] args) { String str = "ab#ab-cab cd"; String[] strings = str.split("#|-| "); //以空格分割 for (String s:strings) { System.out.println(s); } }
結果:
代碼示例: 多次拆分
public static void main(String[] args) { String str = "name=zhangsan&age=18" ; String[] result = str.split("&") ; for (int i = 0; i < result.length; i++) { String[] temp = result[i].split("=") ; System.out.println(temp[0]+" = "+temp[1]); } }
結果:
6.5 字符串截取
代碼:從2下標開始截取
public static void main(String[] args) { String str = "hello"; String ret = str.substring(2); System.out.println(ret);//llo }
public static void main(String[] args) { String str = "hellobit"; String ret = str.substring(2,4); System.out.println(ret);//ll [2,4)表示包含 2 號下標的字符, 不包含 4 號下標 }
注意事項:
- 索引從0開始
- 注意前閉后開區間的寫法, substring(0, 5) 表示包含 0 號下標的字符, 不包含 5 號下標
6.6 其他操作方法
代碼:不使用的時候
public static void main(String[] args) { String str = " a hello"; System.out.println(str); }
結果:
使用的情況:
public static void main(String[] args) { String str = " a hello"; System.out.println(str.trim()); }
結果:去掉了2邊的空格 但是不可以去中間的
代碼:小寫轉大寫
public static void main(String[] args) { String str = "abcde"; System.out.println(str.toUpperCase());//ABCDE }
代碼:
public static void main(String[] args) { String str = "ABCDE"; System.out.println(str.toLowerCase()); //abcde } //都是新對象
代碼:
public static void main(String[] args) { String str = "ABCDE"; System.out.println(str.length()); //5 }
lenth()!=arr.lenth
public static void main(String[] args) { String str = "ABCDE"; String str2 = ""; System.out.println(str.isEmpty()); //false System.out.println(str2.isEmpty()); //true }
7. StringBuffer 和 StringBuilder
首先來回顧下String類的特點:
任何的字符串常量都是String對象,而且String的常量一旦聲明不可改變,如果改變對象內容,改變的是其引用的指向而已。
通常來講String的操作比較簡單,但是由于String的不可更改特性,為了方便字符串的修改,提供StringBuffer和StringBuilder類。
StringBuffer 和 StringBuilder 大部分功能是相同的,我們文章上主要介紹 StringBuffer
在String中使用"+"來進行字符串連接,但是這個操作在StringBuffer類中需要更改為append()方法:
先來看一下StringBuffer和String的區別
- 賦值上的區別 StringBuffer不可以直接賦值
那么需要給它賦值怎么辦?
可以直接new一個給它
- 拼接上面的不同 StringBuffer沒有+號 append 添加
結果:
- 除了append()方法外,StringBuffer也有一些String類沒有的方法
字符串反轉:
深入來看一下 這個代碼:
這個代碼我們說不要這樣使用:因為這樣會產生很多的一個對象,浪費內存
大家可以看見上面 每次都會new一次對象,這樣循環10次了。
我們可以改一下:
這樣就產生了一個new 循環里面沒有那么多new了 節約了內存 達到了一樣的效果
有時候說 String是不可變的 StringBuffer是可變的 為什么呢?
因為String拼接都是新對象
StringBuffer每次拼接返回的是當前對象
而且StringBuffer 和 StringBuilder的區別就是 synchronized 關鍵字
StringBuffer :
StringBuilder:
那么這個synchronized 關鍵字是干嘛的呢??
其實簡單說就是保證線程安全的。
所以說 Stringbuffer適合多線程模式下,StringBuilder適合單線程模式下
注意:String和StringBuffer類不能直接轉換。如果要想互相轉換,可以采用如下原則:
String變為StringBuffer:利用StringBuffer的構造方法或append()方法
StringBuffer變為String:調用toString()方法
面試題:請解釋String、StringBuffer、StringBuilder的區別:
- String的內容不可修改,StringBuffer與StringBuilder的內容可以修改.
- StringBuffer與StringBuilder大部分功能是相似的
- StringBuffer采用同步處理,屬于線程安全操作;而StringBuilder未采用同步處理,屬于線程不安全操作
以上就是我們全部內容了,講的比較詳細,當然有什么不對的地方可以指出來,一起進步感謝觀看!!!
到此這篇關于詳解Java Sring String的常見操作以及StringBuffer StringBuilder的區別的文章就介紹到這了,更多相關Java Sring String 常見操作內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/qq_46874327/article/details/120432146