JVM規範&源碼|字符串長度限制問題


前言

通過閱讀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。

總結

字符串的大小限制問題,常見於前後端對接參數時,字符串傳參而產生的問題。或者,在對接甲方接口時,你根本不知道它給你響應的東西到底有多個,也不做申明,這時總會產生莫名其妙的問題。

(完)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章