在分析String的源碼以前,打算先介紹一點關於JVM的內存分佈,這樣有助於咱們更好地去理解String的設計:
java
Method Area:方法區,當虛擬機裝載一個class文件時,它會從這個class文件包含的二進制數據中解析類型信息,而後把這些類型信息(包括類信息、常量、靜態變量等)放到方法區中,該內存區域被全部線程共享,本地方法區存在一塊特殊的內存區域,叫常量池(Constant Pool)。
Heap:堆是Java虛擬機所管理的內存中最大的一塊。Java堆是被全部線程共享的一塊內存區域,Java中的。
Stack:棧,又叫堆棧或者虛擬機棧。JVM爲每一個新建立的線程都分配一個棧。也就是說,對於一個Java程序來講,它的運行就是經過對棧的操做來完成的。棧以幀爲單位保存線程的狀態。JVM對棧只進行兩種操做:以幀爲單位的壓棧和出棧操做。咱們知道,某個線程正在執行的方法稱爲此線程的當前方法。
Program Count Register:程序計數器,又叫程序寄存器。JVM支持多個線程同時運行,當每個新線程被建立時,它都將獲得它本身的PC寄存器(程序計數器)。若是線程正在執行的是一個Java方法(非native),那麼PC寄存器的值將老是指向下一條將被執行的指令,若是方法是 native的,程序計數器寄存器的值不會被定義。 JVM的程序計數器寄存器的寬度足夠保證能夠持有一個返回地址或者native的指針。
Native Stack:本地方法棧,存儲本地方方法的調用狀態。算法
常量池(constant pool)指的是在編譯期被肯定,並被保存在已編譯的.class文件中的一些數據。它包括了關於類、方法、接口等中的常量,也包括字符串常量。Java把內存分爲堆內存跟棧內存,前者主要用來存放對象,後者用於存放基本類型變量以及對象的引用。數組
先看一下文檔中的註釋。緩存
經過註釋跟繼承關係,咱們知道String被final修飾,並且一旦建立就不能更改,而且實現了CharSequence,Comparable以及Serializable接口。安全
String類經過final修飾,不可被繼承,同時String底層的字符數組也是被final修飾的,char屬於基本數據類型,一旦被賦值以後也是不能被修改的,因此String是不可變的。bash
CharSequence翻譯過來就是字符串,String咱們日常也是叫做字符串,可是前者是一個接口,下面看一下接口裏面的方法:網絡
int length();
char charAt(int index);
CharSequence subSequence(int start, int end);
public String toString();
}複製代碼
方法不多,並無看到咱們常見的String的方法,這個類應該只是一個通用的接口,那麼翻一翻它的實現類
app
private final char value[];//final字符數組,一旦賦值,不可更改
private int hash; //緩存String的 hash Code,默認值爲 0
private static final ObjectStreamField[] serialPersistentFields =new ObjectStreamField[0];//存儲對象的序列化信息複製代碼
public String(){
this.value = "".value;
}
//將數組的值初始化爲空串,此時在棧內存中建立了一個引用,在堆內存中建立了一個對象
//示例代碼
String str = new String()
str = "hello";複製代碼
這種方式實際上建立了兩個對象ide
public String(String original){
this.value = original.value;
this.hash = original.hash;
}
//代碼示例
String str=new String("hello")複製代碼
建立了一個對象函數
public String(char value[]){
//將傳過來的char拷貝至value數組裏面
this.value = Arrays.copyOf(value, value.length);
}複製代碼
不指定編碼
public String(byte bytes[]){
this(bytes, 0, bytes.length);
}
public String(byte bytes[], int offset, int length){
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
static char[] decode(byte[] ba, int off, int len){
String csn = Charset.defaultCharset().name();
try{ //use char set name decode() variant which provide scaching.
return decode(csn, ba, off, len);
} catch(UnsupportedEncodingException x){
warnUnsupportedCharset(csn);
}
try{
//默認使用 ISO-8859-1 編碼格式進行編碼操做
return decode("ISO-8859-1", ba, off, len); } catch(UnsupportedEncodingException x){
//異常捕獲}複製代碼
指定編碼
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;
}複製代碼
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;
}複製代碼
public native String intern();複製代碼
翻譯完了,其實就是一句話,若是常量池中有當前String的值,就返回這個值,若是沒有就加進去,返回這個值的引用,看起來很厲害的樣子。
咱們知道,"+"跟"+="是Java中僅有的兩個重載操做符,除此以外,Java不支持其它的任何重載操做符,下面經過反編譯來看一下Java是如何進行重載的:
public static void main(String[] args) {
String str1="wustor";
String str2= str1+ "Android";
}複製代碼
反編譯Main.java,執行命令 javap -c Main,輸出結果
可能看不懂全部的代碼,可是咱們看到了StringBuilder,而後看到了wustor跟Android,以及調用了StringBuilder的append方法。既然編譯器已經在底層爲咱們進行優化,那麼爲何還要提倡咱們有StringBuilder呢?
咱們仔細觀察一下上面的第三行代碼,new 了一個StringBuilder對象,若是有是在一個循環裏面,咱們使用"+"號進行重載的話就會建立多個StringBuilder的對象,並且,即時編譯器都幫咱們優化了,可是編譯器事先是不知道咱們StringBuilder的長度的,並不能事先分配好緩衝區,也會加大內存的開銷,並且使用重載的時候根據java的內存分配也會建立多個對象,那麼爲何要使用StringBuilder呢,咱們稍後會分析。
我以爲挺好奇,因此接着查看一下若是是char類型的看看switch是怎麼轉換的
public static void main(String[] args) {
char ch = 'a';
switch (ch) {
case 'a':
System.out.println("hello");
break;
case 'b':
System.out.println("world");
break;
default:
break;
}
}複製代碼
基本上跟String差很少,就很少解釋了,由此能夠看出,Java對String的Switch支持實際上也仍是對int類型的支持。
因爲String對象是不可變的,因此在重載的時候會建立多個對象,而StringBuilder對象是可變的,能夠直接使用append方法來進行拼接,下面看看StringBuilder的拼接。
public final class StringBuilder extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
// 空的構造方法
public StringBuilder () {
super(16);
}
//給予一個初始化容量
public StringBuffer(int capacity) {
super(capacity);
}
//使用String進行建立
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
@Override
public StringBuilder append(CharSequence s) {
super.append(s);
return this;
}複製代碼
咱們看到StringBuilder都是在調用父類的方法,並且經過繼承關係,咱們知道它是AbstractStringBuilder 的子類,那咱們就繼續查看它的父類,AbstractStringBuilder 實現了Appendable跟CharSequence 接口,因此它可以跟String相互轉換
char[] value;//字符數組
int count;//字符數量複製代碼
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}複製代碼
能夠看到AbstractStringBuilder只有兩個構造方法,一個爲空實現,還有一個爲指定字符數組的容量,若是事先知道String的長度,而且這個長度小於16,那麼就能夠節省內存空間。他的數組和String的不同,由於成員變量value數組沒有被final修飾因此能夠修改他的引用變量的值,便可以引用到新的數組對象。因此StringBuilder對象是可變的
@Override
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);//檢測容量
value[count++] = c;
return this;
}
//判斷當前字節數組的容量是否知足需求
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
//目前所需容量超出value數組的容量,進行擴容
expandCapacity(minimumCapacity);
}
//開始擴容
void expandCapacity(int minimumCapacity) {
//將現有容量擴充至value數組的2倍多2
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
//若是擴容後的長度比須要的長度還小,則跟須要的長度進行交換
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
//將數組擴容拷貝
value = Arrays.copyOf(value, newCapacity);
}複製代碼
insert也有不少重載方法,下面一樣以char爲例
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; }複製代碼
跟StringBuilder差很少,只不過在全部的方法上面加了一個同步鎖而已,再也不贅述。
equals方法:因爲String從新了Object的equas方法,因此只要兩個String對象的值同樣,那麼就會返回true.
==:這個比較的是內存地址,下面經過大量的代碼示例,來驗證一下剛纔分析的源碼
建立方式 | 對象個數 | 引用指向 |
---|---|---|
String a="wustor" | 1 | 常量池 |
String b=new String("wustor") | 1 | 堆內存 |
String c=new String() | 1 | 堆內存 |
String d="wust"+"or" | 3 | 常量池 |
String e=a+b | 3 | 堆內存 |
valueOf() 轉換爲字符串
trim() 去掉起始和結尾的空格
substring() 截取字符串
indexOf() 查找字符或者子串第一次出現的地方
toCharArray()轉換成字符數組
getBytes()獲取字節數組
charAt() 截取一個字符
length() 字符串的長度
toLowerCase() 轉換爲小寫