1.分類
- 基本屬性:String最終的屬性就一個char[] value;
- 構造方法區域:比較複雜的就是通過unicode碼和byte[]構造;
- 字符串比較:是否相等、大小(排序);
- 查詢:indexOf、startsWith、endWith、contains
- 截取:subString、
- 工具方法:格式化打印、unicode 碼點相關的位操作方法;
2. 基礎
面向對象的封裝思想爲什麼屏蔽了很多底層細節,String類作爲我們使用頻率最高的類,幫我們實現了很多功能,
2.1 不可變性
JDK中有很多不可變的類,所謂的不可變類不是表現在字符串聲明爲final
,而是內部的value是不可以被修改的,所有對value的操作都是返回一個新的對象,比如BigDecimal
,下面是一些錯誤的方法
String str = " hello,你好 ";
str.trim();
str.replace(',',',');//這行是編譯不過去的,中文的char是有問題的
System.out.println(str);
2.2 字符串常量池
JVM爲了提升8種基本類型(short/int/long/double/float/char/boolean/byte)和String的效率,建立了常量池的概念。這些變量在編譯爲class文件後,放入到class文件的常量池部分。JVM運行時會建立字符串常量池,預編譯的字符串會加入常量池,通過intern也可以將字符串加入常量池;
public class StringPoolTest {
public static final String str1 = "a"; // a進入常量池,str1使用常量池引用
public final String str2 = "b"; // b進入常量池,str2使用常量池引用
public String str3 = "c"; // c進入常量池,str3 使用常量池引用
public final String str4 = str2 + str3; // bc不進入常量池, 實際上是 str4 = new StringBuilder(str2).append(str3).toString();
public static void main(String[] args) {
String str5 = "d"; // d進入常量池
final String str7 = "f"; // f 進入常量池
final String str8 = str5 + "j"; //dj不進入常量池 實際上是str8 = new StringBuilder(str5).append(j).toString()
}
}
使用 javap -v
做反編譯查看字節碼的詳細信息如下
public class com.saillen.demo.lang.StringPoolTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
……
#4 = String #44 // c
//這裏很明確引入了StringBuilder,但是我們的代碼中並沒有import
#6 = Class #46 // java/lang/StringBuilder
……
//表示d的引用值在#51定義
#12 = String #51 // d
#13 = String #52 // e
#14 = String #53 // f
#15 = String #54 // j
……
//
#17 = Utf8 str1
#20 = String #56 // a
#21 = Utf8 str2
#22 = Utf8 str3
#23 = Utf8 str4
#35 = Utf8 str5
#36 = Utf8 str6
#37 = Utf8 str7
#38 = Utf8 str8
……
#42 = Utf8 b
#44 = Utf8 c
#51 = Utf8 d
#52 = Utf8 e
#53 = Utf8 f
#54 = Utf8 j
#56 = Utf8 a
{
//類的初始化指令
public com.saillen.demo.lang.StringPoolTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
//加載b,str1的代碼被優化掉了,因爲沒有被使用,還是很智能的
//這裏JDK8優化了無用的code,但是JDK7、6不一定
//優化掉了但是常量池中存在,因爲在其他類中可以使用str1,
4: aload_0
5: ldc #2 // String b
7: putfield #3 // Field str2:Ljava/lang/String;
//加載c
10: aload_0
11: ldc #4 // String c
13: putfield #5 // Field str3:Ljava/lang/String;
//str4 實際是,
//先調用new StringBuilder()
//然後調用append(str2).append(str3)
//然後調用toString()
//然後賦值給str4
16: aload_0
17: new #6 // class java/lang/StringBuilder
20: dup
21: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
//加載b做append(str2)
24: ldc #2 // String b
26: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
//加載c做append(str3)
29: aload_0
30: getfield #5 // Field str3:Ljava/lang/String;
33: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
//toString給str4
36: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
39: putfield #11 // Field str4:Ljava/lang/String;
……
//其他str5、str6、str7、str8同理
}
SourceFile: "StringPoolTest.java"
可以參考:http://blog.csdn.net/sugar_rainbow/article/details/68150249
NOTE:涉及到這種‘基本的’,‘JVM’層次的要注意JDK版本,還有有些結論不一定“正確”;
Unicode 和 UTF-16
- Java使用unicode字符集表示內存中的每個字符(不僅僅是字母和漢字);
- unicode標準中爲每個字符確定了唯一的 碼點(codePoint);
- unicode標準將碼點以16進製表示即\uxxxx形式;
- unicode標準將碼點分區域管理,稱爲平面(plane);
- unicode將最前面的65536字符稱爲 基本平面(BMP);
- unicode僅僅是一個標準,不管實際怎麼存儲的;
- UTF-16僅僅是unicode的一種實現方案;
unicode的幾個問題:
- 碼點如何表示:比如codePoint爲1,那麼1個字節就夠了,但是如果是10萬,那麼1個字節肯定不夠;
- 如何存儲數據?最簡單 or 佔用空間最小?
可以參考:http://blog.csdn.net/gjb724332682/article/details/43229075
問題:爲什麼1byte = 8bit ? 爲什麼1 byte 不用 4bit或者 16bit?
2.3Java中UTF-16
- java使用utf-16存儲和表示字符 – 在JVM中每個字符都是2個字節;
- 2個字節能表示的codePoint 範圍:256 * 256 = 65536,能表示BMP的內容 O~0xFFFF範圍;
- Charset類是char的包裝類也提供了很多Unicode的操作方法,保存了最大和最小字符數量;
- 如果字符比如漢字codePoint的值很大怎麼辦 - 用2個char也就是4個字節來表示;
- 在class中爲了減小文件大小,字符常量都是以UTF8編碼的,但是JVM內存中是UTF16的,即在load的時候是有編碼轉換的;
到底幾個字節?
- char到底是幾個字節,很明確char是2個字節,那麼漢字爲什麼是2個字符,4個字節,如何定義?
- char的實質存儲的是int值,如果codePoint在2個字節內能表示下,那麼就用一個char就夠了,在顯示打印的時候讀取一個char然後找打對應的字符去打印;
- 如果codePoint需要4個字節,2個字符,那麼就讀取兩個連續的char來打印即可。
// 下面的代碼段不要用IDE編輯會報錯。
char c = ''; //不要用IDE編輯。
String s = ""; // char[]的長度爲2;
2,4 Java中char和String到底存儲的是什麼
- String:存儲的是char[]數組;
- char:存儲的是字符的unicode的codePoint的int值;
- java中編碼轉換的實質就是:讀取目標編碼的codePoint的值轉換爲unicode的codePoint值然後按照UTF-16的格式存儲到內存或其他地方;
3 源碼
3.1 屬性
// 字符串是常量,字符串被創建後,字符串的值不可以被修改,
// final的不可修改
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** 存儲了原始字符 */
private final char value[];
/** 因爲字符串的不可變性,所以可以緩存一個hash值 */
private int hash; // Default to 0
private static final long serialVersionUID = -6849794470754667710L;
//序列化使用
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
// 內部方法用來比較字符串大小
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
3.2 常用方法
//判斷長度就是char[]的長度
public int length() {
return value.length;
}
//是不是空串,空格不算空串;
public boolean isEmpty() {
return value.length == 0;
}
//第index個字符,要注意不在BMP內的字符取出的char可能無法轉換爲一個完整字符;
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
//這裏copy了一個新的char,防止String的不可變性被破壞
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
//只copy部分,還是arraycopy
//做數組copy的時候還是多使用arraycopy
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin){
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
3.3 相等性比較、hash
相等的比較思路最終都是:挨個循環比較char[],如果不一致則false
//equals方法挨個比較char字符是否一樣
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
//可以看到是挨個字符計算一個最終的hash
//如果計算了一遍了則不再計算
//對於""的字符串,hashCode就是0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
//將unicode碼參與到計算中
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
3.4 多種valueOf方法
valueOf(Object obj)的時候不會有null的問題,可以替代我們的的obj.toString();
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
public static String valueOf(char data[]) {
return new String(data);
}
public static String valueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}
public static String copyValueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}
public static String copyValueOf(char data[]) {
return new String(data);
}
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
public static String valueOf(char c) {
char data[] = {c};
return new String(data, true);
}
public static String valueOf(int i) {
return Integer.toString(i);
}
public static String valueOf(long l) {
return Long.toString(l);
}
public static String valueOf(float f) {
return Float.toString(f);
}
public static String valueOf(double d) {
return Double.toString(d);
}