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