String類分析
一:簡述:
參考官方API來進行學習String類,String中文意思是字符串。
在java中,String是一個類,而並非是基本類型的數據類型。
String
類代表字符串。Java 程序中的所有字符串字面值(如 "abc",“helloworld” )都作為此類的實例,可以理解成是對象。
二:字符和字符串
眾所周知,計算機的起源是在美國的。美國的所有的單個字符加起來還不到128個。可以在ASCII碼表中查詢到,但是世界上每個國家的文字都並非是一樣的,所有有了編碼格式來對每個國家的文字來進行存儲。根據不同的編碼類型,可以存儲每個國家的文字。
字符串就是將字符串起來的一種表現形式,因為在現實生活中,單個字符不足以用來描述所有的信息,但是字符串是可以的。
官方API中是這樣子來進行解釋的:
String string = "abc";
等價於
char[] chars = {'a','b','c'};
String s = new String(chars);
三:字符串對象
首先寫幾個例子
String s1 = ""; // 沒有空格
String s2 = " "; // 有空格
String s3 = "X";
在上面的幾個案例中,
""," ","X",這幾個都是String類的實例。
四:字符串的操作分類
1、轉換方法
toLowerCase():將字符串轉換為小寫形式這里的大小寫指的是英文大小寫,而不是中文大小寫
toUpperCase():將字符串轉換為大寫形式
toCharArray():將字符串轉換為字符數組(需要一個新的char[]數組來保存)
valueOf(xxx):將xxx類型的數據轉換為String類型(幾乎所有的類型都可以轉)
演示:
public static void main(String[] args) {
// 定義用戶名字
String username = "LiGuang";
// 將username轉換成大寫
String s = username.toLowerCase();
System.out.println(s);
// 將username轉換成小寫
String s1 = username.toUpperCase();
System.out.println(s1);
// 將字符數組轉換成字符串
char[] chars = username.toCharArray();
for (int i = 0; i < chars.length; i++) {
System.out.println(chars[i]);
}
// 將其他類型的數據轉換成String類型。valueOf是一個靜態方法
int num = 97;
String s2 = String.valueOf(num);
if (s2 instanceof String){
System.out.println(s2);
}
}
注意:
valueOf是一個靜態方法,直接用String.valueOf(引用類型的變量)來使用即可;
2、處理方法
trim():去除前字符頭部空白和尾部空白,但是無法去除掉字符串中間空白的
split():對字符串進行分割
concat():與其他字符串進行連接(一般不常用)
replace():替換字符串
3、測試方法
startsWith():測試是否以指定的字符串開頭
endsWith():測試是否以指定的字符串結尾
4、取值方法
length():返回字符串的長度(字符個數)
subString():返回字符串的子字符串(可指定)
charAt():返回字符串中指定索引處的字符
indexOf():返回指定字符在字符串中第一次出現處的索引值
lastIndexOf():返回指定字符在字符串中最后一次出現處的索引值
getBytes():返回字符串的編碼序列(需要一個新的byte[]數組來保存)
5、比較方法
compareTo():以字典順序比較字符串是否相同,區分大小寫
compareToIgnoreCase():以字典順序比較字符串是否相同,忽略大小寫
equals():與指定的對象比較,若相同則返回true,區分大小寫
equalsIgnoreCase():與另一個字符串比較,若相同則返回true,忽略大小寫(注意不是與對象比較)
五:源碼分析
3.1、String類的數據結構
String類的數據結構,最為重要的一點:
private final char value[];
// c++的寫法,改變成java寫法
private final char[] value;
private:表示只能在本類中進行訪問這個數組,在外部類中是無法來進行操作的;
final修飾的的數組(本質上是一個地址,也是一個對象),地址值一旦賦值了,就無法再進行改變了。所以每個字符串一旦賦值之后,那么char就指向了一塊內存空間,那么就再也無法再指向其他的內存空間了。
char[]:說明了java中的字符串對象是用字符數組來進行存儲的。
既然是數組,那么說明了一點。方法肯定有大量的對數組的操作方法。
3.2、hash屬性
既然存在了屬性,那么必然存在着對hash屬性進行操作的方法。
private int hash; // Default to 0
讀到了這里,說明字符串對象要重寫Object類中的hashCode方法了,要有這個敏銳的意識。這里直接去看String類中的hashCode方法。
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
// 首先將原來的數組的地址賦值給新的數組的值
char val[] = value;
// 將里面的每個字符進行相乘和相加
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
// 將最后的h值賦值給hash值
hash = h;
}
return h;
}
這里說明了什么?重寫了hashCode方法,是根據每個字符來進行計算最終的hash值的。
那么這種情況,可能存在着不同的字符串的hash值是一樣的。
String a="Aa";
String b="BB";
int aa=a.hashCode();
System.out.println("aa的hash值"+aa);
int bb=b.hashCode();
System.out.println("bb的hash值"+bb);
System.out.println(aa==bb);
System.out.println(a.hashCode()==b.hashCode());
System.out.println(a.hashCode()==b.hashCode());
控制台結果:
aa的hash值2112
bb的hash值2112
true
true
說明了什么問題???根據字符的值來進行計算的hashCode值是一樣的。但是因為這里的hashCode值是相同的,但是這里的hashCode算法是重寫過的,所以說並不能作為是同一個對象的條件。
在java中,認為Object類中的hashCode相同的是同一個對象。那么String類重寫了這個方法,會認為hash值是一樣的是同一個對象嗎?接着往下看。
看下String類型的equals方法
public boolean equals(Object anObject) {
// 對於引用類型來說,比較的是在內存中的地址值是否是一樣的
if (this == anObject) {
return true;
}
// 判斷anOjbect是否是String的,如果是的,判斷語句中
if (anObject instanceof String) {
// 強制類型轉換
String anotherString = (String)anObject;
// 判斷length
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;
}
總結:
兩個字符串要是相等,底層采用的是在內存中的地址相等或者是每個字符都是一樣的;
四、構造函數
第一種:
public String() {
this.value = "".value;
}
如果直接使用:
String string = new String();
那么new出來的是一個空字符串,指向的也是內存中的一個空字符串。
小結:
final修飾的數組指向的地址是無法改變的,也就表示了對象是無法改變的了
但是對於對象的引用來說,它不是固定的。它是可以進行改變的。
第二種:
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
看下源碼:
public static char[] copyOf(char[] original, int newLength) {
// new了一個新的數組出來
char[] copy = new char[newLength];
// 將原來數組中的元素賦值一份到新建的數組中去
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
所以說,這個字符串底層的數組和原來的數組不是同一個。這也是必須的,不然外界改了數組中的值,那么字符串中的內容也就改了。hash是使用數組中字符依次計算得到的值。
第二個:拿到一部分字符,應該不常用
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
4.1、面試題幾道題型
Java 語言提供對字符串串聯符號("+")以及將其他對象轉換為字符串的特殊支持。字符串串聯是通過 StringBuilder
(或 StringBuffer
)類及其 append
方法實現的
例子一:
String s1 = "helloworld";
String s2 = "helloworld";
例子二:
String s1 = "hello";
String s2 = "world";
String s3 = s1+s2;
System.out.print("helloworld"==s1+s2) // false
相當於:
s1+s2========String s3 =new String("helloworld")
System.out.print(s3=="helloworld");
將堆內存中的地址和字符串常量池的內存進行相比較
例子三:
String s1 = "hello";
final String s2 = "world";
String s3 = s1+s2;
System.out.print(3=="helloworld");
相當於:
s1+s2========String s3 =new String("helloworld")
System.out.print(s3=="helloworld");
將堆內存中的地址和字符串常量池的內存進行相比較
例子四:
final String s1 = "hello";
final String s2 = "world";
System.out.print("helloworld"==s1+s2); //true
對於兩個字符串字符串常量來說,JVM對其進行了優化。
例子五:
final String s1 = null
final String s2 = "world";
sout("helloworld"==s1+s2); // true
例子六:
String s1 = null;
String s2 = "hello";
System.out.println(s1+s2);
總結:
1、final修飾的字符串變量相加會優化
2、兩個字符串對象相加會優化
3、兩個非final修飾的相加,在堆內存中產生了新的對象
4、直接寫字符串對象,字符串對象存在於常量池;new的存在於堆內存中。
五、常用方法
一、獲取得到字符串長度
public int length() {
return value.length;
}
觀察發現,底層調用的是length的值。需要注意的是這個數組,length就是數組中元素的個數,這是靜態數組和動態數組的區別。
二、判斷字符串是否是空的
public boolean isEmpty() {
return value.length == 0;
}
三、根據下標來取值
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
得到指定下標的元素值
四、將字符串底層數組中的字符轉換成字節數組
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}
第五個:兩個字符串比較。必須都是String類型的字符串
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值進行賦值
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
這里的hashCode計算出來的hash值,是根據字符數組中的字符來進行計算的。
六、轉換成字符串對象
public String toString() {
return this;
}
六、Integer源碼
整數常量池
主要分析這里的一段代碼
private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {}}
獲取得到值的時候
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i);}
優先會從這里來進行查詢;
如果沒有找到,才會去內存中創建一個新的地址來存儲整數。