其實String方面的面試題往深了延申的話,仍是會延伸到JVM,因此仍是但願讀者對JVM有必定的瞭解,這樣更便於理解String的設計。
/* Strings are constant; their values can not be changed after they are created. Stringbuffers support mutable strings.Because String objects are immutable they can be shared. Forexample: */ public final class String implements java.io.Serializable, Comparable<String>, CharSequence
源碼裏能夠看到String被final修飾並繼承了三個接口 源碼註釋也說到字符串是不變的; 它們的值在建立後沒法更改.Stringbuffers支持可變字符串。 由於String對象是不可變的,因此它們能夠共享
修飾類:類不可被繼承,也就是說,String類不可被繼承了 修飾方法:把方法鎖定,以訪任何繼承類修改它的涵義 修飾遍歷:初始化後不可更改
Serializable接口裏爲空 Comparable接口裏只有一個public int compareTo(T o);方法 這兩個接口不作敘述.
接口中的方法 length(): int charAt(): char subSequence(int,int):CharSwquence toString(): String chars(): IntStream codePoints(): IntStream
咱們發現這個接口中的方法不多,沒有咱們經常使用的String方法, 那麼它應該是一個通用接口,會有不少實現類,包括StringBuilder和StringBuffer
public String() { this.value = "".value; }
解析java
String str=new String("abc"); 1.先建立一個空的String對象 2.常量池中建立一個abc,並賦值給第二個String 3.將第二個String的引用傳遞給第一個String 注:若是常量池中有abc,則不用建立,直接把引用傳遞給第一個String
public String(String original) { this.value = original.value; this.hash = original.hash; } 案例: String str=new String("str");
public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } 注:將傳過來的char數組copy到value數組裏
byte類型的方法有8個,兩個過期的
剩下六個又分爲指定編碼和不指定編碼面試
不指定編碼算法
public String(byte bytes[]) { this(bytes, 0, bytes.length); } public String(byte bytes[], int offset, int length, Charset charset) { if (charset == null) throw new NullPointerException("charset"); checkBounds(bytes, offset, length); this.value = StringCoding.decode(charset, bytes, offset, length); }
指定編碼數組
String(byte bytes[], Charset charset) String(byte bytes[], String charsetName) String(byte bytes[], int offset, int length, Charset charset) String(byte bytes[], int offset, int length, String charsetName)
解析安全
byte是網絡傳輸或存儲的序列化形式, 因此在不少傳輸和存儲的過程當中須要將byte[]數組和String進行相互轉化, byte是字節,char是字符,字節流和字符流須要指定編碼,否則可能會亂碼, bytes字節流是使用charset進行編碼的,想要將他轉換成unicode的char[]數組, 而又保證不出現亂碼,那就要指定其解碼方法
public String(StringBuffer buffer) { synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } } public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); } 注:不少時候咱們不會這麼去構造,由於StringBuilder跟StringBuffer有toString方法 若是不考慮線程安全,優先選擇StringBuilder
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; }
String重寫了父類Object的equals方法 先判斷地址是否相等(地址相等的狀況下,確定是一個值,直接返回true) 在判斷是不是String類型,不是則返回false 若是都是String,先判斷長度, 再比較值,把值賦給char數組,遍歷兩個char數組比較
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; }
若是String的length==0或者hash值爲0,則直接返回0 若是上述條件不知足,則經過算法計算hash值
public native String intern(); 注:方法註釋會有寫到,意思就是調用方法時, 若是常量池有當前String的值,就返回這個值,沒有就加進去,返回這個值的引用
String str1="a"; String str2="b"; String str3="ab"; String str4 = str1+str2; String str5=new String("ab"); System.out.println(str5==str3);//堆內存比較字符串池 //intern若是常量池有當前String的值,就返回這個值,沒有就加進去,返回這個值的引用 System.out.println(str5.intern()==str3);//引用的是同一個字符串池裏的 System.out.println(str5.intern()==str4);//變量相加給一個新值,因此str4引用的是個新的 System.out.println(str4==str3);//變量相加給一個新值,因此str4引用的是個新的 false true false false
重點: --兩個字符串常量或者字面量相加,不會new新的字符串,其餘相加則是新值,(如 String str5=str1+"b";)網絡
由於在jvm翻譯爲二進制代碼時,會自動優化,把兩個值後邊的結果先合併,再保存爲一個常量。
String裏還有不少方法,substring,replace等等,咱們就不一一舉例了多線程
StringBuilder和Stringbuffer這兩個類的方法都很想同樣,所以咱們就那StringBuilder的源碼做分析 等下再去看三者之間的關係和不一樣
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{ //空構造,初始大小16 public StringBuilder() { super(16); } //給予一個初始化容量 public StringBuilder(int capacity) { super(capacity); } //使用String進行建立 public StringBuilder(String str) { super(str.length() + 16); append(str); } //String建立和CharSequence類型建立,額外多分配16個字符的空間, //而後調用append將參數字符添加進來,(字符串緩衝區) public StringBuilder(CharSequence seq) { this(seq.length() + 16); append(seq); } }
解析app
咱們能夠看到方法內部都是在調用父類的方法, 經過繼承關係,咱們是知道它的父類是AbstractStringBuilder, 父類裏實現類Appendable跟CharSequence 接口,因此它可以跟String相互轉換
AbstractStringBuilder() { } AbstractStringBuilder(int capacity) { value = new char[capacity]; }
解析jvm
父類裏是隻有兩個構造方法,一個爲空實現,一個爲指定字符數組的容量, 若是事先知道String的長度小於16,就能夠節省內存空間, 他的數組和String的不同,由於成員變量value數組沒有被final修飾, 因此能夠修改他的引用變量的值,便可以引用到新的數組對象, 因此StringBuilder對象是可變的
append有不少重載方法,原理都差很少 咱們以String舉例 //傳入要追加的字符串 public AbstractStringBuilder append(String str) { //判斷字符串是否爲null if (str == null) return appendNull(); //不爲null,得到它的長度 int len = str.length(); //調用方法,把原先長度+追加字符串長度的和傳入方法 ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } //判斷是否知足擴展要求 private void ensureCapacityInternal(int minimumCapacity) { // 和-原先字符串長度是否>0 確定是大於0的 if (minimumCapacity - value.length > 0) //調用複製空間的方法,和當參數 expandCapacity(minimumCapacity); } //開始擴充 void expandCapacity(int minimumCapacity) { //先把原先長度複製2倍多2 int newCapacity = value.length * 2 + 2; //判斷newCapacity-和是否<0 if (newCapacity - minimumCapacity < 0) //小於0的狀況就是你複製的長度不夠,那就把和的長度給複製的長度 newCapacity = minimumCapacity; //正常邏輯怎麼着都走不到這一步,新長度確定是大於0 if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } //將數組擴容拷貝 value = Arrays.copyOf(value, newCapacity); }
insert一樣有不少重載方法,下面以char和String爲例 insert的ensureCapacityInternal(count + 1);和上面同樣,不作講解了 public AbstractStringBuilder insert(int offset, char c) { //檢查是否知足擴充條件 ensureCapacityInternal(count + 1); //拷貝數組 System.arraycopy(value, offset, value, offset + 1, count - offset); //進行復制 value[offset] = c; count += 1; return this; } public AbstractStringBuilder insert(int offset, String str) { //判斷要插入的座標是否在字符串內,再也不則報數組下標越界 if ((offset < 0) || (offset > length())) throw new StringIndexOutOfBoundsException(offset); //判斷要插入的是否爲null if (str == null) str = "null"; //得到要插入的字符串長度 int len = str.length(); //檢查是否知足擴充條件 ensureCapacityInternal(count + len); //拷貝數組 System.arraycopy(value, offset, value, offset + len, count - offset); str.getChars(value, offset); count += len; return this; }
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence { } 跟StringBuilder差很少,只不過在全部的方法上面加了一個同步鎖
String類重寫了父類equals的方法
咱們先看下父類的源碼分析
//直接判斷地址 public boolean equals(Object obj) { return (this == obj); }
再看下String類的equals
public boolean equals(Object anObject) { //地址相等確定爲true,就不用繼續往下走了 if (this == anObject) { return true; } //地址不相等的狀況下,比較兩個字符的內容是否同樣 //把字符串方法char[]數組裏,遍歷比較 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; }
==比較的是內存地址
基本數據類型比較值 引用數據類型比較地址值 (對象的引用,在堆空間,String在字符串池,newString在堆空間)
根據下面案例分析一下源碼
建立方式 | 對象個數 | 引用指向
String a="abc"; |1 | 常量池
String b=new String("abc");; |1 | 堆內存 (abc則是複製的常量池裏的abc)
String c=new String() |1 | 堆內存
String d="a"+"bc"; |3 | 常量池(a一次,bc一次,和一次,d指向和)
String e=a+b; |3 | 堆內存
重點--兩個字符串常量或者字面量相加,不會new新的字符串,
變量相加則是會new新的字符串,new出來的都在堆
String被final修飾,一旦建立沒法更改,每次更改則是在新建立對象 StringBuilder和StringBuffer則是可修改的字符串 StringBuilder和StringBuffer的區別 StringBuffer 被synchronized 修飾,同步,線程安全 StringBuilder並無對方法進行加同步鎖,因此是非線程安全的。 若是程序不是多線程的,那麼使用StringBuilder效率高於StringBuffer。