String類可以說是Java中最常使用的類了,但是String所包含的知識也是相當的多。
首先爲什麼String類一定要用equals()方法來比較字符串呢?還有new 出來的字符串和""雙印號的字符串有什麼區別呢?
看看下面這個例子
String s1 = new String("helloworld");
String s2 = new String("helloworld");
String s3 = "helloworld";
String s4 = "helloworld";
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s3 == s4);
System.out.println("");
System.out.println(s1.intern() == s3);
System.out.println(s1.intern() == s3.intern());
System.out.println("");
String s5 = new String("hello");
String s6 = "hello";
System.out.println(s5 == s6);
s5 = s5.intern();
System.out.println(s5 == s6);
首先s1.equals(s2)是肯定是對的,等會我們分析equals的源代碼。這裏就不寫出來了
s1 == s2 肯定是false 因爲==只是比較兩個String的引用 因爲new出來 都是在堆上分配空間,所以是false
s1 == s2 不對 是因爲 s3是在運行時放進常量區 也就是方法區裏面的常量池,一個是堆上 一個是在常量區 所以false
s3 == s4 是true 是因爲 代碼經過編譯後 認爲是同一個常量 所以指向的引用地址相同。
s1.intern() == s3 和 s1.intern() == s3.intern() 返回true 是因爲intern()函數查詢常量池,如果常量池不存在,則放入常量池,然後返回引用,若是已經存在該常量則返回存在引用,所以相等
第一個s5 == s6 因爲s5指向堆而s6指向常量區所以地址不同 false
第二個s5 == s6 因爲s5變爲常量區的引用 所以true
說到這裏我想應該知道new出來的和""創建的String類型有什麼不同了嗎?一個是在編譯時候賦予引用值,一個是在運行時決定。那麼String對象到底是可變的嗎?
一般來說String類型是不可變的,大多數情況下,String的正常操作String的子串,拼接都是返回一個新的字串。但是可以通過反射改變其值
Field field = s1.getClass().getDeclaredField("value");
field.setAccessible(true);
char[] value =(char[]) field.get(s5);
value[1]='c';
System.out.println(s5);
System.out.println(s6);
通過反射成功的改變了s5和s6的值。他們都是公用同一個常量引用。
輸出爲
hcllo
hcllo
還有不要濫用String類的charAt方法
爲什麼呢?char類型採用unicode編碼 即2個字節表示一個字符即 最多能表達 65536個字符 但是目前的字符遠遠超過這麼多
對於特殊字符來說,好吧 這裏我上傳的代碼段應該是非正常字符,所以被過濾導致後面的都被截斷,只能重寫,上圖了
所以對於字符處理使用codePointAt()比較於安全
源碼分析
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
這裏調用了Character的codePointAtImpl()方法轉換codePoint編碼
static int codePointAtImpl(char[] a, int index, int limit) {
char c1 = a[index];
if (isHighSurrogate(c1) && ++index < limit) {
char c2 = a[index];
if (isLowSurrogate(c2)) {
return toCodePoint(c1, c2);
}
}
return c1;
}
public static int toCodePoint(char high, char low) {
// Optimized form of:
// return ((high - MIN_HIGH_SURROGATE) << 10)
// + (low - MIN_LOW_SURROGATE)
// + MIN_SUPPLEMENTARY_CODE_POINT;
return ((high << 10) + low) + (MIN_SUPPLEMENTARY_CODE_POINT
- (MIN_HIGH_SURROGATE << 10)
- MIN_LOW_SURROGATE);
}
通過比較適否是正常字符,是則直接返回,不是則將char[i]和char[i+1]合併成新的編碼
然後還需要注意的則是一般字符串的編碼,這種情況在網絡流的情況下遇到比較多
String base = "why君";
byte[] utf8s = base.getBytes("utf-8");
for (int i = 0; i < utf8s.length; i++) {
System.out.printf("%02x ", utf8s[i]);
}
System.out.println();
byte[] gbks = base.getBytes("gbk");
for (int i = 0; i < gbks.length; i++) {
System.out.printf("%2x ", gbks[i]);
}
System.out.println();
byte[] utf16s = base.getBytes("utf-16");
for (int i = 0; i < utf16s.length; i++) {
System.out.printf("%02x ", utf16s[i]);
}
System.out.println();
對於 why君 不同的編碼產生的效果也是不同的
w h y 君
utf-8 77 68 79 e5 90 9b
gbk 77 68 79 be fd
utf-16(fe ff) 00 77 00 68 00 79 54 1b
feff爲utf頭封裝
最後爲String的equals方法分析
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;
}
是否指向同一個對象引用,是true
是否String 然後比較長度是否相等 相等 則繼續逐字符比較