Java(String篇)

1、String類
String類 不可被繼承,是不可改變的,被final修飾。一旦創建了String對象,那它的值就無法改變了。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    private final char value[];					//該值用於字符存儲
    private int hash;							//緩存字符串的哈希碼。默認是0
    
	private static final long serialVersionUID = -6849794470754667710L;	
	
	private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

	//構造器

	public String() {
        this.value = "".value;
    }


	 public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

 	public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
}

可以看出String類的定義基本符合不可變類的特點,只有"hash"存在疑問。

    /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    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;
    }

hash變量用於緩存字符串的哈希嗎。而hashCode()方法用於得到字符串對象的哈希嗎。由上面的源碼可以看出,hash雖然不是final,但是它是通過計算final屬性value得來的,並且能保證每次調用它的值都是一致的。所以String是不可變的。

2、Sting類內存是怎麼存儲的

先上一段代碼

package JavaBase;

public class StringTest {
	
	public static void main(String[] args) {	
		
		String str = "Hello,World!";
		String str2 = "Hello,World!";
		
		String str3 = new String("Hello,World!");
		String str4 = new String("Hello,World!");
		
		System.out.println(str == str2);			//true
		System.out.println(str.equals(str2));		//true
		
		System.out.println(str == str3);			//false
		System.out.println(str.equals(str3));		//true
		
		System.out.println(str4 == str3);			//false
		System.out.println(str4.equals(str3));		//true
	}

	
}

內存過程大致如下

1)運行先編譯,然後當前類StringTest .class文件加載進入內存的方法區
2)main方法壓入棧內存
3)常量池創建一個"Hello,World!“對象,產生一個內存地址。
4)然後吧"Hello,World!“內存地址複製給main方法成員變量str1,這個時候str1根據內存地址,指向了常量池的"Hello,World!”。
5)運行到String str2 = “Hello,World!”;,由於常量池存在"Hello,World!”,所以不會在創建,直接把"Hello,World!"內存地址賦值給了str2。
6)接下來new會在堆內存區域創建"Hello,World!“對象,把內存地址複製給str3,指向堆的"Hello,World!”。
7)再次new的時候,也會再次創建一個"Hello,World!“對象,把內存地址複製給str4,指向堆的"Hello,World!”。
8)此時堆中存在兩個對象。分別有着自己的內存地址,自己的內存區域。

java中的equals比較對象的值,==比較對象的內存地址(可參考鏈接)。 輸出顯而易見。
在這裏插入圖片描述
假如 String str4 = “Hello,” + “World!”;內存分配,與str1比較的結果又是怎樣的呢?
字符串是一個特殊的包裝類,其引用是存放在棧裏的,而對象內容必須根據創建方式不同定(常量池和堆),有的是編譯期就已經創建好,存在字符串常量池中,而有的是運行時候才被創建,使用new關鍵字,存放在堆中。
可參考: java+內存分配及變量存儲位置的區別
java內存分配和String類型的深度解析
可是有一個疑惑

public class StringTest {
	
	public static void main(String[] args) {	
		
		String str = "Hello,World!";
		String s1 = "Hello,";
		String s2 = "World!";
		
		String s = "Hello," + "World!";
		
		String str2 = s1 + s2;
		
		System.out.println(str2 	== str);
		System.out.println(s 		== str);
		System.out.println(str2 	== s);
		
		System.out.println(s1 == "Hello,");
	}
}
運行結果:
false
true
false
true

關於 s 與 str2 內存分配的情況又是怎樣的呢?

3、字符串判斷是否是迴文串?

	public static boolean isPalindrome(String str) {
		if(str == null) {
			return false;
		}
		StringBuilder strBuilder = new StringBuilder(str);
		strBuilder.reverse();
		return strBuilder.toString().equals(str);
	}
	
	public static boolean _isPalindrome(String str) {
		if(str == null) {
			return false;
		}
		int length = str.length();
		for (int i = 0; i < length/2; i++) {
			if(str.charAt(i) != str.charAt(length - i - 1)) {
				return false;
			}
		}
		return true;
	}

4、如何比較兩個字符串?


 public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

  public boolean equalsIgnoreCase(String anotherString) {
        return (this == anotherString) ? true
                : (anotherString != null)
                && (anotherString.value.length == value.length)
                && regionMatches(true, 0, anotherString, 0, value.length);
   }
    
    
  public boolean regionMatches(boolean ignoreCase, int toffset,
            String other, int ooffset, int len) {
        char ta[] = value;
        int to = toffset;
        char pa[] = other.value;
        int po = ooffset;
        // Note: toffset, ooffset, or len might be near -1>>>1.
        if ((ooffset < 0) || (toffset < 0)
                || (toffset > (long)value.length - len)
                || (ooffset > (long)other.value.length - len)) {
            return false;
        }
        while (len-- > 0) {
            char c1 = ta[to++];
            char c2 = pa[po++];
            if (c1 == c2) {
                continue;
            }
            if (ignoreCase) {
                // If characters don't match but case may be ignored,
                // try converting both characters to uppercase.
                // If the results match, then the comparison scan should
                // continue.
                char u1 = Character.toUpperCase(c1);
                char u2 = Character.toUpperCase(c2);
                if (u1 == u2) {
                    continue;
                }
                // Unfortunately, conversion to uppercase does not work properly
                // for the Georgian alphabet, which has strange rules about case
                // conversion.  So we need to make one last check before
                // exiting.
                if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                    continue;
                }
            }
            return false;
        }
        return true;
    }
  • compareTo(String anotherString)
    與傳入的anotherString字符串進行比較,如果小於傳入的字符串返回負數,如果大於則返回正數。當兩個字符串值相等時,返回0.此時eqauls方法會返回true。

  • equalsIgnoreCase(String str)
    該方法與compareTo方法類似,區別只是內部利用了Character.toUpperCase等方法進行了大小寫轉換後進行比較。

5、將String轉爲char,將char轉爲String?

  • 將String轉爲char:
    使用String.charAt(int index)(返回值爲char)可以得到String中某一指定位置的char。
    使用String.toCharArray()(返回值爲char[])可以包含整個String的char數組。
  • char轉爲String
    String s = String.valueOf(‘c’);最高效
    String s = String.valueOf(new Char[]{‘c’});將一個char數組轉爲String
    String s = Character.toString(‘c’)
    String s = new Character(‘c’).toString();
    String s = “” + ‘c’; 這個方法很簡單,但是效率最低。Java中String Object的值實際上是不可變的,是一個final變量,所以每次對String做出任何改變,都是初始化了一個全新的String Object並將原來的變量指向了這個新的String。而java對使用+運算符處理String相加進行了方法重載。字符串直接相加連接實際上調用了 new StringBuilder().append("").append(‘c’).toString();

6、淺談一下String, StringBuffer,StringBuilder的區別
String 是不可變類,每當我們對String進行操作的時候,總會是會創建新的字符串。操作String很消耗資源,所以Java提供了兩個工具類來操作String,StringBuffer,StringBuilder。

StringBuffer 是線程安全的,而StringBuilder是線程不安全的。

  • StringBuffer
public final class StringBuffer  extends AbstractStringBuilder 
	implements java.io.Serializable, CharSequence
{

    public synchronized StringBuffer append(StringBuffer sb) {
        toStringCache = null;
        super.append(sb);
        return this;
    }



    @Override
    public synchronized int lastIndexOf(String str, int fromIndex) {
        return super.lastIndexOf(str, fromIndex);
    }

   @Override
    public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }

由源碼可以看出,一些操作字符串的接口都被synchronize修飾,所以效率比較低,線程安全。

  • StringBuilder:
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{


    @Override
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

    @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }
    

可見StringBuilder是線程不安全的,效率相對於StringBuffer也比較高。

7、String的intern()方法
返回:
字符串對象的規範化表示形式;
一個字符串,內容與此字符串相同,但它保證來自字符串池中。

	public static void main(String[] args) {	
  	
  	String str = "Hello,World!";
  	String s1 = "Hello,";
  	String s2 = "World!";

  	String str2 = s1 + s2;
  	
  	String str3 = new String("Hello,World!");
  	
  	System.out.println(str2.intern());	
  	System.out.println(str3.intern());
  	
  	System.out.println(str2.intern() == str);
  	System.out.println(str2 == str);
  	
  	System.out.println(str3.intern() == str);
  	System.out.println(str3 == str);

  	}

  運行結果:
  Hello,World!
  Hello,World!
  true
  false
  true
  false

儘管在輸出中調用intern()並沒有什麼效果,但是實際上後臺這個方法會做一系列的動作和操作。在調用這個方法時,會先檢查字符池(常量池)中是否有這個字符串,如果有則返回這個字符串的引用,否則就將這個字符串添加到字符串池中,然後返回這個字符串的引用。
它遵循對於任何兩個字符串str1和str2,當且僅當str1.equals(str2)爲true時,str1.intern() == str2.intern()才爲true。所有字面值字符串和字符串賦值常量表達式都是內部的。

8、String是線程安全的嗎?

String是不可變類,一旦創建了String類,我們就無法改變它的值,因此,它是線程安全的,可在多線程的環境下使用。

9、String的HashCode()計算方式

String重寫了父類Object的hashCode()方法。Object的該方法被native修飾( public native int hashCode()),被native的方法一般屬於原生函數,是用c/c++實現的,然後生成一個dll文件供java調用。

    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;
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章