String類
1、String概述
- 字符串
- 常量,創建之後不能更改,改變的只是地址
//String的屬性值
private final char value[];
//數組被使用的開始位置
private final int offset;
//String中元素的個數
private final int count;
//String類型的hash值
private int hash; // Default to 0
- 從源碼看出String底層使用一個字符數組來維護的。
- 成員變量可以知道String類的值是final類型的,不能被改變的,所以只要一個值改變就會生成一個新的String類型對象,存儲String數據也不一定從數組的第0個元素開始的,而是從offset所指的元素開始。
java
String的構造方法
String()
//初始化一個新創建的 String 對象,使其表示一個空字符序列。
String(byte[] bytes)
//通過使用平臺的默認字符集解碼指定的 byte 數組,構造一個新的 String。
String(byte[] bytes, Charset charset)
//通過使用指定的 charset 解碼指定的 byte 數組,構造一個新的 String。
String(byte[] bytes, int offset, int length)
//通過使用平臺的默認字符集解碼指定的 byte 子數組,構造一個新的 String。
String(byte[] bytes, int offset, int length, Charset charset)
//通過使用指定的 charset 解碼指定的 byte 子數組,構造一個新的 String。
String(byte[] bytes, int offset, int length, String charsetName)
//通過使用指定的字符集解碼指定的 byte 子數組,構造一個新的 String。
String(byte[] bytes, String charsetName)
//通過使用指定的 charset 解碼指定的 byte 數組,構造一個新的 String。
String(char[] value)
//分配一個新的 String,使其表示字符數組參數中當前包含的字符序列。
String(char[] value, int offset, int count)
//分配一個新的 String,它包含取自字符數組參數一個子數組的字符。
String(int[] codePoints, int offset, int count)
//分配一個新的 String,它包含 Unicode 代碼點數組參數一個子數組的字符。
String(String original)
//初始化一個新創建的 String 對象,使其表示一個與參數相同的字符序列;換句話說,新創建的字符串是該參數字符串的副本。
String(StringBuffer buffer)
//分配一個新的字符串,它包含字符串緩衝區參數中當前包含的字符序列。
String(StringBuilder builder)
//分配一個新的字符串,它包含字符串生成器參數中當前包含的字符序列。
2、創建字符串對象方式
- 直接賦值(在方法區的常量池)
String str = "hello";
- 通過構造方法(實例化)創建在堆內存
String str = new String("hello");
“==”和equals()兩種方式的比較
- 編寫代碼比較
public static void main(String[] args) {
String str1 = "Lance";
String str2 = new String("Lance");
String str3 = str2; //引用傳遞,str3直接指向st2的堆內存地址
String str4 = "Lance";
/**
* ==:
* 基本數據類型:比較的是基本數據類型的值是否相同
* 引用數據類型:比較的是引用數據類型的地址值是否相同
* 所以在這裡的話:String類對象==比較,比較的是地址,而不是內容
*/
System.out.println(str1==str2);//false
System.out.println(str1==str3);//false
System.out.println(str3==str2);//true
System.out.println(str1==str4);//true
}
- ==
- 比較基本數據類型的值
- 比較引用數據類型的地址是否相同
- equals()
- 比較的是字符串內容
- 2、內存圖分析
- str1先存入常量池中,str4因為常量池中存在相同的內容所以直接拿來使用
- str2和str3在堆內存中,引用(地址)相同
字符串常量池
- 採用直接賦值的方式
String str1 = "Lance"
時,會將匿名對象放入對象池。- 每當下一次對不同的對象進行直接賦值的時候會直接利用池中原有的匿名對象
- 手動入池
String str = new String("Lance").intern();
public static void main(String args[]){
//對匿名對象"hello"進行手工入池操作
String str =new String("Lance").intern();
String str1="Lance"; // 此時會直接利用池中原有的匿名變量
System.out.println(str==str1);//true
}
兩種創建方式的區別
- 直接賦值:只開闢一塊堆內存空間,並且會自動入池,不會產生垃圾
- 構造方法:會開闢兩塊堆內存空間
(相當於先直接賦值創建匿名對象“hello”一次再構造方法創建一次),其中一塊堆內存會變成垃圾被系統回收,而且不能夠自動入池,需要通過public String intern();
方法進行手動入池- 在開發的過程中不會採用構造方法進行字符串的實例化
3、String的方法
1、String的判斷
boolean equals(Object obj):比較字符串的內容是否相同
boolean equalsIgnoreCase(String str): 比較字符串的內容是否相同,忽略大小寫
boolean startsWith(String str): 判斷字符串對象是否以指定的str開頭
boolean endsWith(String str): 判斷字符串對象是否以指定的str結尾
public static void main(String[] args) {
// 創建字符串對象
String s1 = "hello";
String s2 = "hello";
String s3 = "Hello";
// boolean equals(Object obj):比較字符串的內容是否相同
System.out.println(s1.equals(s2)); //true
System.out.println(s1.equals(s3)); //false
System.out.println("-----------");
// boolean equalsIgnoreCase(String str):比較字符串的內容是否相同,忽略大小寫
System.out.println(s1.equalsIgnoreCase(s2)); //true
System.out.println(s1.equalsIgnoreCase(s3)); //true
System.out.println("-----------");
// boolean startsWith(String str):判斷字符串對象是否以指定的str開頭
System.out.println(s1.startsWith("he")); //true
System.out.println(s1.startsWith("ll")); //false
}
2、String的截取
int length():獲取字符串的長度,其實也就是字符個數
char charAt(int index):獲取指定索引處的字符 類似於數組取數
int indexOf(String str):獲取str在字符串對象中第一次出現的索引,返回位置,下標從0開始
String substring(int start):從start開始截取字符串
String substring(int start,int end):從start開始,到end結束截取字符串。包括start,不包括end
public static void main(String args[]) {
// 創建字符串對象
String s = "helloworld";
// int length():獲取字符串的長度,其實也就是字符個數
System.out.println(s.length()); //10
System.out.println("--------");
// char charAt(int index):獲取指定索引處的字符
System.out.println(s.charAt(0)); //h
System.out.println(s.charAt(1)); //e
System.out.println("--------");
// int indexOf(String str):獲取str在字符串對象中第一次出現的索引
System.out.println(s.indexOf("l")); //2
System.out.println(s.indexOf("owo")); //4
System.out.println(s.indexOf("ak")); //-1
System.out.println("--------");
// String substring(int start):從start開始截取字符串
System.out.println(s.substring(0)); //helloworld
System.out.println(s.substring(5)); //world
System.out.println("--------");
// String substring(int start,int end):從start開始,到end結束截取字符串
// [start,end)
System.out.println(s.substring(0, s.length())); //helloworld
System.out.println(s.substring(3, 8)); //lowor
}
3、String的轉換
char[] toCharArray():把字符串轉換為字符數組
String toLowerCase():把字符串轉換為小寫字符串
String toUpperCase():把字符串轉換為大寫字符串
public static void main(String args[]) {
// 創建字符串對象
String s = "abcde";
// char[] toCharArray():把字符串轉換為字符數組
char[] chs = s.toCharArray();
for (int x = 0; x < chs.length; x++) {
System.out.println(chs[x]);
}
System.out.println("-----------");
// String toLowerCase():把字符串轉換為小寫字符串
System.out.println("HelloWorld".toLowerCase());
// String toUpperCase():把字符串轉換為大寫字符串
System.out.println("HelloWorld".toUpperCase());
}
其他方法
去除字符串兩端空格:String trim()
按照指定符號分割字符串:String[] split(String str)
public static void main(String args[]) {
// 創建字符串對象
String s1 = "helloworld";
String s2 = " helloworld ";
String s3 = " hello world ";
System.out.println("---" + s1 + "---");
System.out.println("---" + s1.trim() + "---");
System.out.println("---" + s2 + "---");
System.out.println("---" + s2.trim() + "---");
System.out.println("---" + s3 + "---");
System.out.println("---" + s3.trim() + "---");
System.out.println("-------------------");
// String[] split(String str)
// 創建字符串對象
String s4 = "aa,bb,cc";
String[] strArray = s4.split(",");
for (int x = 0; x < strArray.length; x++) {
System.out.println(strArray[x]);
}
}
輸出結果:
---helloworld---
---helloworld---
--- helloworld ---
---helloworld---
--- hello world ---
---hello world---
-------------------
aa
bb
cc
4、String的不可變性
- 不可變的好處:
- 可以實現多個變量引用堆內存中的同一個字符串實例,避免創建的開銷
- 出於安全性考慮
- HashMap中的key為String類型
- 當我們在傳參的時候,使用不可變類不需要去考慮誰可能會修改其內部的值,如果使用可變類的話,可能需要每次記得重新拷貝出裡面的值,性能會有一定損失
示例
這裡採用享元模式(共享元素的模式,一個系統中如果有多處用到了相同的元素,只需要儲存一次,讓所有地方都使用這一個),當使用直接賦值的方式時,每生成一個對象就將其存入共享的常量池(String Pool)內,當第二次生成同樣內容的時候就共享此對象而不是創建新的
5、字符串常量池
1、常量池表(Constant_Pool table)
- Class文件中存儲所有常量(包括字符串)的table
- 其實就是Class文件中的字節碼指令
2、運行時常量池(Runtime Constant Pool)
- JVM內存中方法區的一部分,這是運行時的內容,這部分內容(絕大部分)是隨著JVM運行的時候,從常量池轉化而來,每個Class對應一個運行時常量池
- 除了Class中常量池內容,還可能包括動態生成並加入這裡的內容
3、字符串常量池(String Pool)
- 是JVM實例全局共享的,全局只有一個,也在方法區中
- JVM規範要求進入這裡的String實例叫“被駐留的Interned string”,各個JVM可以有不同的實現,HotSpot是設置了一個哈希表StringTable來引用堆中的字符串實例,被引用就是被駐留
6、字符串創建的詳細分析
int x = 10;
String y = "hello";
- 1、首先10和"hello"會在經過javac(或其他編譯器)編譯過後變為class文件中
constant_pool table
的內容- 2、當我們的程序(JVM)運行時,每個Class
constant_pool table
中的內容會被加載到JVM內存中的方法區中各自Class的Runtime Constant Pool
- 3、一個沒有被String Pool包含的Runtime Constant Pool中的字符串("hello")會被加入到String Pool中(HotSpot使用hashtable引用方式),步驟如下:
- 在Java Heap(堆)中根據"hello"字面量create一個字符串對象
- 將字面量"hello"與字符串對象的引用(地址)在hashtable中關聯起來鍵-值
形式是:“hello”=對象的引用地址
- 如何判斷一個新的字符串出現在Runtime Constant Pool中是否需要在Java Heap中創建新對象呢?
會先根據equals來比較Runtime Constant Pool中的這個字符串是否和StringPool中某一個相等,如果有就不創建直接引用,如果沒有就執行第三步- 例如:
使用String s = new String("hello");會創建幾個對象
答:會創建2個對象
首先,出現了字面量"hello",那麼去String Pool中查找是否有相同字符串存在.
// 因為"hello"是匿名對象,所以要用到直接賦值的創建方式,去常量池中查找
因為程序就這一行,所以肯定沒有,那麼就在Java Heap中用字面量"hello"首先創建1個String對象。
接著,new String("hello"),關鍵字new又在Java Heap中創建了1個對象,然後調用接收String
參數的構造器進行了初始化。
最終s的引用是後面這個String對象.
6、StringBuffer和StringBuilder
1、概述
- StringBuilder:
- (單線程)非線程安全的,不能同步訪問
- 可變的字符序列
- 執行速度最快
- 繼承於AbstractStringBuilder
- 實現了CharSequence接口
- StringBuffer:
- 多線程安全的(可以同步訪問)
- 繼承於AbstractStringBuilder
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
}