String的總結


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);}

優先會從這里來進行查詢;

如果沒有找到,才會去內存中創建一個新的地址來存儲整數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM