Java中String字符的長(zhǎng)度有限制嗎纱兑?
這是面試中有可能會(huì)被問(wèn)到的問(wèn)題碑隆,對(duì)于這個(gè)問(wèn)題答案,和class文件有關(guān)对雪。
class文件
Java提供了一種在多個(gè)平臺(tái)都能使用的一種中間代碼--字節(jié)碼類文件(.class文件)
所以不管是什么Java虛擬機(jī)語(yǔ)言怜庸,最終都需要通過(guò)編譯器將源代碼編譯成class文件当犯。這樣也就解除了多種語(yǔ)言與Java虛擬機(jī)之間的耦合。
而通過(guò)整體角度看割疾,class文件里只有兩種數(shù)據(jù)結(jié)構(gòu):無(wú)符號(hào)數(shù)和表
無(wú)符號(hào)數(shù)
無(wú)符號(hào)數(shù)屬于基本的數(shù)據(jù)類型嚎卫。以u(píng)1、u2宏榕、u4拓诸、u8來(lái)分別代表一個(gè)字、2個(gè)字節(jié)担扑、4個(gè)字節(jié)和8個(gè)字節(jié)的無(wú)符號(hào)數(shù)官份。
無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字浑塞、索引引用、數(shù)量值或者字符串(UTF-8編碼)
表
有多個(gè)無(wú)符號(hào)或者其他表作為數(shù)據(jù)項(xiàng)構(gòu)成復(fù)合數(shù)據(jù)類型填大。
class文件中的所有表都以“_info”結(jié)尾首有。
整個(gè)class文件本質(zhì)就是一張表燕垃。
class文件結(jié)構(gòu)
class文件結(jié)構(gòu)由無(wú)符號(hào)數(shù)和表組成,并且結(jié)構(gòu)按照預(yù)先規(guī)定好的順序進(jìn)行緊密的排列井联。并且都是有16進(jìn)制字節(jié)碼構(gòu)成卜壕。
當(dāng)JVM加載class文件時(shí),就是按照上圖順序進(jìn)行解析烙常,并進(jìn)行相應(yīng)的內(nèi)存分配轴捎,分配大小如下圖。
實(shí)例分析
通過(guò)對(duì)文件結(jié)構(gòu)了解蚕脏,結(jié)合實(shí)例進(jìn)行分析侦副。如下Java實(shí)例代碼
import java.io.Serializable;
public class Test implements Serializable, Cloneable{
private int num = 1;
public int add(int i) {
int j = 10;
num = num + i;
return num;
}
}
通過(guò) javac 將其編譯,生成 Test.class 字節(jié)碼文件驼鞭。然后使用 16 進(jìn)制編輯器打開(kāi) class 文件秦驯,顯示內(nèi)容如下所示:
魔數(shù)
在class文件開(kāi)頭的四個(gè)字節(jié)是class文件的魔數(shù),它是一個(gè)固定值 --0XCAFEBABE
魔數(shù)是class文件標(biāo)志挣棕,JVM虛擬機(jī)通過(guò)開(kāi)頭四個(gè)字節(jié)的魔數(shù)值來(lái)判斷是否是class文件译隘。如果不是這個(gè)值亲桥,則表示當(dāng)前不是class文件,不能被JVM識(shí)別和加載固耘。
版本號(hào)
魔數(shù)后面思維則是版本號(hào)题篷,而這個(gè)版本號(hào)對(duì)應(yīng)的是jdk的版本號(hào)。
前兩個(gè)字節(jié)0000代表次版本號(hào)厅目,后兩個(gè)字節(jié)0034是主版本號(hào)悼凑,對(duì)應(yīng)的十進(jìn)制數(shù)為52。當(dāng)前class文件住版本號(hào)為52璧瞬,此版本為0户辫,所以中和版本號(hào)是52.0,也就是jdk1.8.0嗤锉。
常量池
版本號(hào)后面是一個(gè)叫做常量池的表(cp_info)渔欢,在常量池中報(bào)春了類各種相關(guān)信息。比如類的名稱瘟忱、父類的名稱奥额、類中的方法名、參數(shù)名稱访诱、參數(shù)類型等垫挨。
常量池表分析
CONSTANT_Class_info 表具體結(jié)構(gòu)如下所示:
table CONSTANT_Class_info {
u1 tag = 7;
u2 name_index;
}
tag占用一個(gè)字節(jié)大小哲泊,上面tag=7,說(shuō)明是CONSTANT_Class_info類型表催蝗,也就是類/接口的引用表切威。
而name_index則是一個(gè)索引值,可以理解為一個(gè)指針丙号,指向常量池中索引為name_index的常量池表先朦。比如name_index=2,則它指向常量池第二個(gè)常量犬缨。
接下來(lái)再看 CONSTANT_Utf8_info 表具體結(jié)構(gòu)如下:
table CONSTANT_utf8_info {
u1 tag=1;
u2 length;
u1[] bytes;
}
tag=1喳魏,表示是CONSTANT_Utf8_info類型表,length表示下面u1[]數(shù)組的長(zhǎng)度遍尺。
如果在Java代碼中聲明的是String字符串截酷,最終class文件中的存儲(chǔ)格式則是CONSTANT_utf8_info。因此一個(gè)字符串最大長(zhǎng)度也就是u2所能代表的最大值,也就是65536個(gè)迂苛。去掉保持null值的兩個(gè)三热,最終String一個(gè)字符串最大長(zhǎng)度為65536-2=65534
在常量池內(nèi)部的表中也有相互之間的引用。用一張圖來(lái)理解 CONSTANT_Class_info 和 CONSTANT_utf8_info 表格之間的關(guān)系三幻,如下圖所示:結(jié)合上面的分析,就能解析版本號(hào)后面的常量池信息:
class文件在常量池的前面使用2個(gè)字節(jié)的容器計(jì)數(shù)器念搬,用來(lái)代表當(dāng)前類中的常量池大小抑堡。001d轉(zhuǎn)化為十進(jìn)制為29,也就是常量計(jì)數(shù)器值為29朗徊,其中下表為0的常量被JVM用作與其他特殊用途首妖,因此Test.class中時(shí)機(jī)的常量池大小為29-1=28
第一個(gè)常量,如下所示:
0a 轉(zhuǎn)化為 10 進(jìn)制后為 10爷恳,通過(guò)查看常量池 14 種表格圖中有缆,可以查到 tag=10 的表類型為 CONSTANT_Methodref_info,因此常量池中的第一個(gè)常量類型為方法引用表温亲。其結(jié)構(gòu)如下:
CONSTANT_Methodref_info {
u1 tag = 10;
u2 class_index; 指向此方法的所屬類
u2 name_type_index; 指向此方法的名稱和類型
}
在“0a”之后的 2 個(gè)字節(jié)指向這個(gè)方法是屬于哪個(gè)類棚壁,緊接的 2 個(gè)字節(jié)指向這個(gè)方法的名稱和類型。它們的值分別是:
- 0006:十進(jìn)制 6栈虚,表示指向常量池中的第 6 個(gè)常量袖外。
- 0015:十進(jìn)制 21,表示指向常量池中的第 21 個(gè)常量魂务。
訪問(wèn)標(biāo)志
緊跟著常量池之后的是訪問(wèn)標(biāo)志曼验,占兩個(gè)字節(jié)。
訪問(wèn)標(biāo)志代表類或者借口的訪問(wèn)信息头镊。比如該class文件是類還是接口蚣驼,是否被定義為public,是否是abstract相艇。如果是類,是否被聲明成final等纯陨。
Test.java 是一個(gè)普通 Java 類坛芽,不是接口、枚舉或注解翼抠。并且被 public 修飾但沒(méi)有被聲明為 final 和 abstract咙轩,因此它所對(duì)應(yīng)的 access_flags 為 0021(0X0001 和 0X0020 相結(jié)合)。
類索引阴颖、父類索引與接口索引計(jì)數(shù)器
在訪問(wèn)標(biāo)志后的 2 個(gè)字節(jié)就是類索引活喊,類索引后的 2 個(gè)字節(jié)就是父類索引,父類索引后的 2 個(gè)字節(jié)則是接口索引計(jì)數(shù)器量愧。
可以看出類索引指向常量池中的第 5 個(gè)常量钾菊,父類索引指向常量池中的第 6 個(gè)常量帅矗,并且實(shí)現(xiàn)的接口個(gè)數(shù)為 2 個(gè)。再回顧下常量池中的數(shù)據(jù):
從圖中可以看出煞烫,第 5 個(gè)常量和第 6 個(gè)常量均為 CONSTANT_Class_info 表類型浑此,并且代表的類分別是“Test”和“Object”。再看接口計(jì)數(shù)器滞详,因?yàn)榻涌谟?jì)數(shù)器的值是 2凛俱,代表這個(gè)類實(shí)現(xiàn)了 2 個(gè)接口。查看在接口計(jì)數(shù)器之后的 4 個(gè)字節(jié)分別為:
- 0007:指向常量池中的第 7 個(gè)常量料饥,從圖中可以看出第 7 個(gè)常量值為"Serializable"蒲犬。
- 0008:指向常量池中的第 8 個(gè)常量,從圖中可以看出第 8 個(gè)常量值為"Cloneable"岸啡。
綜上所述原叮,可以得出如下結(jié)論:當(dāng)前類為 Test 繼承自 Object 類,并實(shí)現(xiàn)了“Serializable”和“Cloneable”這兩個(gè)接口凰狞。
字段表
緊跟在接口索引集合后面的就是字段表了篇裁,字段表的主要功能是用來(lái)描述類或者接口中聲明的變量。這里的字段包含了類級(jí)別變量以及實(shí)例變量赡若,但是不包括方法內(nèi)部聲明的局部變量达布。
同樣, 一個(gè)類中的變量個(gè)數(shù)是不固定的,因此在字段表集合之前還是使用一個(gè)計(jì)數(shù)器來(lái)表示變量的個(gè)數(shù)逾冬,如下所示:
0002 表示類中聲明了 2 個(gè)變量(在 class 文件中叫字段)黍聂,字段計(jì)數(shù)器之后會(huì)緊跟著 2 個(gè)字段表的數(shù)據(jù)結(jié)構(gòu)。
字段表的具體結(jié)構(gòu)如下:
CONSTANT_Fieldref_info{
u2 access_flags 字段的訪問(wèn)標(biāo)志
u2 name_index 字段的名稱索引(也就是變量名)
u2 descriptor_index 字段的描述索引(也就是變量的類型)
u2 attributes_count 屬性計(jì)數(shù)器
attribute_info
}
字段訪問(wèn)標(biāo)識(shí)
對(duì)于 Java 類中的變量身腻,也可以使用 public产还、private、final嘀趟、static 等標(biāo)識(shí)符進(jìn)行標(biāo)識(shí)脐区。因此解析字段時(shí),需要先判斷它的訪問(wèn)標(biāo)識(shí)她按,字段的訪問(wèn)標(biāo)識(shí)如下所示:
字段表結(jié)構(gòu)圖中的訪問(wèn)標(biāo)志的值為 0002牛隅,代表它是 private 類型。變量名索引指向常量池中的第 9 個(gè)常量酌泰,變量名類型索引指向常量池中第 10 個(gè)常量媒佣。第 9 和第 10 個(gè)常量分別為“num”和“I”,如下所示:
因此可以得知類中有一個(gè)名為 num陵刹,類型為 int 類型的變量默伍。
注意事項(xiàng):
- 字段表集合中不會(huì)列出從父類或者父接口中繼承而來(lái)的字段。
- 內(nèi)部類中為了保持對(duì)外部類的訪問(wèn)性,會(huì)自動(dòng)添加指向外部類實(shí)例的字段也糊。
方法表
字段表之后跟著的就是方法表常量炼蹦。方法表常量應(yīng)該也是以一個(gè)計(jì)數(shù)器開(kāi)始的,因?yàn)橐粋€(gè)類中的方法數(shù)量是不固定的显设,如圖所示:
上圖表示 Test.class 中有兩個(gè)方法框弛, Test.java 中聲明了一個(gè) add 方法,另外一個(gè)是默認(rèn)構(gòu)造器方法捕捂。
方法表的結(jié)構(gòu)如下所示:
CONSTANT_Methodref_info{
u2 access_flags; 方法的訪問(wèn)標(biāo)志
u2 name_index; 指向方法名的索引
u2 descriptor_index; 指向方法類型的索引
u2 attributes_count; 方法屬性計(jì)數(shù)器
attribute_info attributes;
}
方法也是有自己的訪問(wèn)標(biāo)志瑟枫,具體如下:
add 方法,具體如下:
add 方法的以下字段的具體值:
- access_flags = 0001 也就是訪問(wèn)權(quán)限為 public指攒。
- name_index = 0X0011 指向常量池中的第 17 個(gè)常量慷妙,也就是“add”。
- type_index = 0X0012 指向常量池中的第 18 個(gè)常量允悦,也即是 (I)膝擂。這個(gè)方法接收 int 類型參數(shù),并返回 int 類型參數(shù)隙弛。
屬性表
在之前解析字段和方法的時(shí)候架馋,在它們的具體結(jié)構(gòu)中我們都能看到有一個(gè)叫作 attributes_info 的表,就是屬性表全闷。
屬性表并沒(méi)有一個(gè)固定的結(jié)構(gòu)叉寂,各種不同的屬性只要滿足以下結(jié)構(gòu)即可:
CONSTANT_Attribute_info{
u2 name_index;
u2 attribute_length length;
u1[] info;
}
Code屬性表
可以看到,在方法類型索引之后跟著的就是“add”方法的屬性总珠。0X0001 是屬性計(jì)數(shù)器屏鳍,代表只有一個(gè)屬性。0X000f 是屬性表類型索引局服,通過(guò)查看常量池可以看出它是一個(gè) Code 屬性表钓瞭,如下所示:
Code 屬性表中,最主要的就是一些列的字節(jié)碼淫奔。通過(guò) javap -v Test.class 之后山涡,可以看到方法的字節(jié)碼,如下圖顯示的是 add 方法的字節(jié)碼指令:
JVM 執(zhí)行 add 方法時(shí)唆迁,就通過(guò)這一系列指令來(lái)做相應(yīng)的操作佳鳖。