Java中的String

来源:互联网 发布:开源大数据调度系统 编辑:程序博客网 时间:2024/04/30 06:44

Literal strings within the same class in the same package represent references to the same String object.

Literal strings within different classes in the same package represent references to the same String object.

Literal strings within different classes in different packages likewise represent references to the same String object.

Strings computed by constant expressions are computed at compile time and then treated as if they were literals.

Strings computed by concatenation at run time are newly created and therefore distinct.

The result of explicitly interning a computed string is the same string as any pre-existing literal string with the same contents.

首先解释下什么是字符串字面常数(String Literals),字面常数(Literals)就是你写在源代码里面的值,比如说int i = 6; 6就是一个整数形字面常数。String s = "abc"; “abc”就是一个字符串字面常数。

Java中,所有的字符串字面常数都放在上文提到的字符串池里面,是可以共享的,就是说,String s1 = "abc"; String s2 = "abc"; s1,s2都引用的同一个字符串对象,而且这个对象在字符串池里面,因此s1=s2。

        String str_b = new String("hello");        String str_a = "hello";

String类自己维护了一个池(string pool),把程序中的String对象放到那个池里面,于是我们代码中只要用到值相同的String,就是同一个String对象,节省了空间。

对于第一种,jvm会马上在heap中创建一个String对象,然后将该对象的引用返回给用户。

对于第二种,jvm首先会在内部维护的string pool中通过String的 equals( ) 方法查找string pool中是否存放有该String对象,如果有,则返回已有的String对象给用户,而不会在heap中重新创建一个新的String对象;如果string pool中没有该String对象,jvm则在heap中创建新的String对象,将其引用返回给用户,同时将该引用添加至string pool中。注意:使用第一种方法创建对象时,jvm是不会主动把该对象放到string pool里面的,除非程序调用 String的intern( )方法。

        String s = "hello";    //    0    0:ldc1            #2   <String "hello">    //    1    2:astore_1    

ldc指令格式:ldc  index

执行ldc指令时,JVM首先查找index所指定的常量池入口,对于上面的“hello”,JVM会找到CONSTANT_String_info,生成一个String对象,并将该对象的引用压入操作数栈。

astore_1指令格式:astore_1

执行astore_1指令时,JVM从操作数栈顶部弹出一个引用类型,然后将该值存入由索引“1”指定的局部变量中,即将引用类型的值存入局部变量1。

        String s1 = new String("hello");    //    2    3:new             #3   <Class java.lang.String>    //    3    6:dup                 //    4    7:ldc1            #2   <String "hello">    //    5    9:invokespecial   #4   <Method void String(java.lang.String)>    //    6   12:astore_2            //    7   13:return    

new指令格式:new indexbyte1,indexbyte2

执行new指令时,JVM通过计算((indextype1<<8) | indextype2)生成一个指向常量池的无符号16位索引。然后JVM根据计算出的索引查找常量池入口。该索引所指向的常量池入口必须为CONSTANT_Class_info。如果该入口尚不存在,那么JVM将解析这个常量池入口。JVM在堆创建一个String的新对象,将该对象的引用压入操作数栈。

dup指令格式:dup

执行dup指令时,JVM复制了操作数栈顶部一个字长的内容,然后再将复制内容压入栈,即复制String对象的引用,这时在操作数栈存在2个String的引用。

ldc指令格式:ldc  index

执行ldc指令时,JVM首先查找index所指定的常量池入口,对于上面的“hello”,JVM会找到CONSTANT_String_info,生成一个String对象,并将该对象的引用压入操作数栈。

invokespecial指令格式:invokespecial   indextype1,  indextype2

Operand Stack:..., objectref, [arg1, [arg2 ...]]  ...

The objectref must be of type reference and must be followed on the operand stack bynargs argument values, where the number, type, and order of the values must be consistent with the descriptor of the selected instance method.

JVM通过计算((indextype1<<8) | indextype2)生成一个指向常量池的无符号16位索引,The runtime constant pool item at that index must be a symbolic reference to a method。

astore_1指令格式:astore_1

要执行astore_1指令,JVM从操作数栈顶部弹出一个引用类型或者returnAddress类型值,然后将该值存入由索引1指定的局部变量中,即将引用类型或者returnAddress类型值存入局部变量1。


关于String的常见问题:

【1】

String a = "a1";
String b = "a" + 1;
System.out.println((a == b)); //result = true
String a = "atrue";
String b = "a" + "true";
System.out.println((a == b)); //result = true
String a = "a3.4";
String b = "a" + 3.4;
System.out.println((a == b)); //result = true

在编译期,JVM就将常量字符串的"+"连接优化为连接后的值,拿"a" + 1来说,经编译器优化后在class中就已经是a1。

【2】
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false

在"+"连接时,由于有对象引用存在,而引用的值在编译期是无法确定的,所以"a" + bb无法被编译器优化,只有在运行时来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。

【3】
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = true

对于final修饰的,而且在编译时已经能够确定值的对象引用,也会被优化,上面程序的结果为true。

【4】
String a = "ab";
final String bb = getBB();
String b = "a" + bb;
System.out.println((a == b)); //result = false
private static String getBB() {
return "b";
}

对于final修饰的,无法在编译时就确定值的对象引用,不会被优化,故上面程序的结果为false。

【5】

String a = "ab";
String s1 = "a";
String s2 = "b";
String s = s1 + s2;
System.out.println(s == a);//false
System.out.println(s.intern() == a);//true 
对于s1+s2操作,其实是在堆里面重新创建了一个新的对象,s保存的是这个新对象的引用,所以s与a的值是不相等的。而当调用s.intern()方法,却可以返回s在常量池中的地址值,故s.intern和a的值相等。

String的长度
   String对象最多能容纳多少字符呢?查看String的源代码我们可以得知String中是使用域 count 来记录对象字符的数量,而count 的类型为 int,因此,我们可以推测最长的长度为 2^31,也就是2G。

   String在内存的长度也受它内部维护的 char[] 数组的最大元素个数影响,Java的数组下标只能是有符号的int,所以理论的最大个数应该是 2^31。

   不过,我们在编写源代码的时候,如果使用 Sting str = "aaaa";的形式定义一个字符串,那么双引号里面的ASCII字符最多只能有 65534 个。为什么呢?因为在class文件的规范中, CONSTANT_Utf8_info表中使用一个16位的无符号整数来记录字符串的长度的,最多能表示 65536个字节。而class 文件是使用一种变体UTF-8格式来存放字符的,null值使用两个字节来表示,因此只剩下 65536- 2 = 65534个字节。也正是变体UTF-8的原因,如果字符串中含有中文等非ASCII字符,那么双引号中字符的数量会更少(一个中文字符占用三个字节)。如果超出这个数量,在编译的时候编译器会报错。

原创粉丝点击