前言
通過閱讀JVM規範和源碼,我們可以知道,String無論是字面量定義的形式還是運行時生成的方式都是有限制的。
Javac(eclipse編譯方式可能作了些修改)編譯階段,字面量定義的字符串形式需要小於65535,運行時階段大概小於2^31,4個G左右。
分析
如圖所示,先動態的輸出10w個1,然後copy出來,以字面量的形式定義一個字符串s,然後輸出,此時會報錯。
報錯原因爲字符串過長,即編譯階段,字面量形式定義的字符串是會有限制的。
有小夥伴可能說爲啥我的就沒有報錯呢,可能這裏需要將編譯方式修改爲javac的方式1。
字符串常量池
通過閱讀java虛擬機規範,我們可以知道字符串常量池的形式如下圖表示:
首先,字符串常量池的實現和Map有着異曲同工直面,以哈希表和鏈表的方式進行實現。其中,tag是一個固定值8(常量池有很多種,每種都有特定數字進行表示)
而關於string_index則爲索引值,指向相應的字符串序列,其結構可以用Constant_Utf8.info進行表示。其中,u2爲無符號的2字節,2^15+…+ 2^0=65535。即通過字面量定義的字符串最爲65534.
通過對編譯時的代碼進行調試,我們可以找到一個checkStringConstant
方法,在編譯階段,進入常量池的字符串會在這裏做成判斷,如果大於等於65535,會輸出錯誤日誌。
private void checkStringConstant(DiagnosticPosition var1, Object var2) {
if (this.nerrs == 0 && var2 != null && var2 instanceof String && ((String)var2).length() >= 65535) {
this.log.error(var1, "limit.string", new Object[0]);
++this.nerrs;
}
}
運行時的字符串大小
當我們繞過編譯器,來到運行時的階段,我們的字符串大小的限制就遠遠超過65535了,我們可以看如下的代碼,它的輸出是沒有問題的:
public static void main(String[] args) {
String s = "";
for (int i = 0; i < 100000; i++) {
s+=i;
}
System.out.println(s);
}
爲什麼呢?原來通過“+”運行,我們的編譯器會優化成:
new StringBuilder().append("XX")...append("XX").toString()
再看看StringBuilder源碼中的toString方法,它會調用String的構造方法public String(char value[], int offset, int count)
:
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
關於該構造方法,代碼如下所示:
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);
}
看到這裏,機制的小夥伴應該發覺原因了吧,其實在StringBuild階段我們大概就可以猜出來了,因爲count表示了字符串的字符個數,而這個count是有長度限制的,它用int進行表示,通過查看它的“裝箱類”Integer
源碼:
/**
* A constant holding the maximum value an {@code int} can
* have, 2<sup>31</sup>-1.
*/
@Native public static final int MAX_VALUE = 0x7fffffff;
我們可以知道它的最大值爲2^31-1,這個數字的字符串大概有多大呢?
首先字符串在其底層,也是用一個char數組進行存儲的,而char,一個字符大小相當於2個字節,16位。2^31-1個字符爲:
2^31-1 * 16 / 8 /1024 /1024/1024 = 3.99....
大小約等於4G。
總結
字符串的大小限制問題,常見於前後端對接參數時,字符串傳參而產生的問題。或者,在對接甲方接口時,你根本不知道它給你響應的東西到底有多個,也不做申明,這時總會產生莫名其妙的問題。
(完)