论坛首页 综合技术论坛

【总结】String in Java

浏览 37137 次
该帖已经被评为良好帖
作者 正文
   发表时间:2009-11-24  
先赞一个,Q:
1. 我咋看不到字节流图。
2.还请博主揭开“四个不同类型的常量表”的面纱。
0 请登录后投票
   发表时间:2009-11-24  
"最后将堆中StringBuilder对象的地址赋给变量sab",sab是一个string,能指导到一个stringbuilder?
0 请登录后投票
   发表时间:2009-11-25   最后修改:2009-11-25
mwei 写道
先赞一个,Q:
1. 我咋看不到字节流图。
2.还请博主揭开“四个不同类型的常量表”的面纱。



1、class文件是二进制字节流。其结构是按顺序组成的有意义的项。比如说class文件字节流中最开始的4个字节是magic项,后面4个字节是主、次Version项,然后是Constant pool常量池项....   。字节流逻辑图就是(http://dl.iteye.com/upload/picture/pic/50015/1f7bd098-2b41-3f95-b129-460190f351ba.gif)左侧部分。

2、所谓“四个不同类型的常量表”,是指在这篇文章中我用HelloWord代码做例子,这段代码的.class文件在常量池中有四种不同类型的常量表组成。其实常量表的种类远不只这些。在《深入Java虚拟机》第二版第6章P124有详细说明。这里帖点出来:
                                       【表   常量池标志】
                入口类型                        标志值                    描述
             CONSTANT_Utf8                         1                  UTF-8编码的字符串
             CONSTANT_Integer                      3                  int类型字面值
             CONSTANT_Float                        4                  float类型字面值
             CONSTANT_Long                         5                  long类型字面值
             CONSTANT_Double                       6                  double类型字面值
             CONSTANT_Class                        7                  对一个类或接口的符号引用
             CONSTANT_String                       8                  String类型字面值
             CONSTANT_Fieldref                     9                  对一个字段的符号引用
             CONSTANT_Methodref                    10                 对一个类中声明的方法的符号引用
             CONSTANT_InterfaceMethodref           11                 对一个接口中声明的方法的符号引用
             CONSTANT_NameAndType                  12                 对一个字段或方法的部分符号引用
0 请登录后投票
   发表时间:2009-11-25  
Heart.X.Raid 写道


1、class文件是二进制字节流。其结构是按顺序组成的有意义的项。比如说class文件字节流中最开始的4个字节是magic项,后面4个字节是主、次Version项,然后是Constant pool常量池项....   。字节流逻辑图就是(http://dl.iteye.com/upload/picture/pic/50015/1f7bd098-2b41-3f95-b129-460190f351ba.gif)左侧部分。

2、所谓“四个不同类型的常量表”,是指在这篇文章中我用HelloWord代码做例子,这段代码的.class文件在常量池中有四种不同类型的常量表组成。其实常量表的种类远不只这些。在《深入Java虚拟机》第二版第6章P124有详细说明。这里帖点出来:
                                       【表   常量池标志】
                入口类型                        标志值                    描述
             CONSTANT_Utf8                         1                  UTF-8编码的字符串
             CONSTANT_Integer                      3                  int类型字面值
             CONSTANT_Float                        4                  float类型字面值
             CONSTANT_Long                         5                  long类型字面值
             CONSTANT_Double                       6                  double类型字面值
             CONSTANT_Class                        7                  对一个类或接口的符号引用
             CONSTANT_String                       8                  String类型字面值
             CONSTANT_Fieldref                     9                  对一个字段的符号引用
             CONSTANT_Methodref                    10                 对一个类中声明的方法的符号引用
             CONSTANT_InterfaceMethodref           11                 对一个接口中声明的方法的符号引用
             CONSTANT_NameAndType                  12                 对一个字段或方法的部分符号引用

昨天网络不行,没看到图。现已看到图,清楚多了,谢谢博主。
钻研精神&&分享心理一般是大师所有。
0 请登录后投票
   发表时间:2009-11-25   最后修改:2009-11-25
midstr 写道
"最后将堆中StringBuilder对象的地址赋给变量sab",sab是一个string,能指导到一个stringbuilder?


请看下例子

public class StringTest {

	public static void main(String[] args) {
		String s1 = "a";
		String s2 = "a2";
		String s3 = s1+s2;
		String s4 = "aa2";
		System.out.println(s3==s4);
		
	}
}



StringTest的class文件
public static void main(java.lang.String[] args);
     0  ldc <String "a"> [16]
     2  astore_1 [s1]
     3  ldc <String "a2"> [18]
     5  astore_2 [s2]
     6  new java.lang.StringBuilder [20]
     9  dup
    10  aload_1 [s1]
    11  invokestatic java.lang.String.valueOf(java.lang.Object) : java.lang.String [22]
    14  invokespecial java.lang.StringBuilder(java.lang.String) [28]
    17  aload_2 [s2]
    18 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder[31]
    21  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [35]
    24  astore_3 [s3]
    25  ldc <String "aa2"> [39]
    27  astore 4 [s4]
    29  getstatic java.lang.System.out : java.io.PrintStream [41]
    32  aload_3 [s3]
    33  aload 4 [s4]
    35  if_acmpne 42
    38  iconst_1
    39  goto 43
    42  iconst_0
    43  invokevirtual java.io.PrintStream.println(boolean) : void [47]
    46  return

    18 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder[31]

请注意红色标注,谢谢LZ分享!
0 请登录后投票
   发表时间:2009-11-26  
受益匪浅啊!
0 请登录后投票
   发表时间:2009-11-27  
楼主,关于ldc指令的解释,最好强调一下,ldc “Push item from runtime constant pool”,即该指令将常量池中的“东西”push到栈。因为java的指令是基于栈的而不是基于寄存器的嘛。下面的方面需要用到这个push到栈的“东西”。
0 请登录后投票
   发表时间:2009-11-28  
Heart.X.Raid 写道

作者:Java标准类库有几千个类,唯独String不太一样。为什么这么说?就因为每次上网冲杯Java时,都能看到关于String无休无止的争论。还是觉得有必要让这个讨厌又很可爱的String美眉,赤裸裸的站在我们这些Java色狼面前了。嘿嘿....

众所周知,String是由字符组成的串,在程序中使用频率很高。Java中的String是一个类,而并非基本数据类型。 不过她却不是普通的类哦!!!

 

【镜头1】 String对象的创建

   1、关于类对象的创建,很普通的一种方式就是利用构造器,String类也不例外:
           String s=new String("Hello world");
   问题:参数"Hello world"是什么东西,也是字符串对象吗?莫非用字符串对象创建一个字符串对象???

   2、当然,String类对象还有一种大家都很喜欢的创建方式:
           String s="Hello world";
   问题:有点怪呀,怎么与基本数据类型的赋值操作(int i=1)很像呀???

在开始解释这些问题之前,我们先引入一些必要的知识:
(1) Java class文件结构
       我们都知道,Java程序要运行,首先需要编译器将源代码文件编译成字节码文件(也就是.class文件)。然后在由JVM解释执行。
       class文件是8位字节的二进制流 。这些二进制流的涵义由一些紧凑的有意义的项 组成。比如class字节流中最开始的4个字节组成的项叫做魔数 (magic),其意义在于分辨class文件(值为0xCAFEBABE)与非class文件。class字节流大致结构如下图左侧。

                               

      其中,在class文件中有一个非常重要的项——常量池 。这个常量池专门放置源代码中的常量信息(并且不同的常量存放在不同标志的常量表中)。如上图右侧是HelloWorld代码中的常量表(HelloWorld代码如下),其中有四个不同类型的常量表(四个不同的常量池入口)。关于常量池的具体细节,请参照《深入Java虚拟机》第二版第6章。

public class HelloWorld{
	void hello(){
		System.out.println("Hello world");
	}
}

      显然,HelloWorld代码中的"Hello world"被编译之后,可以清楚的看到存放在了class二进制流的常量池项中(上图右侧红框区域)。并且我们还发现常量池中专门有为String类型设置的常量表 。也就是说,在编译阶段,就已经将代码中的这种("****")形式作为了字符串常量存放在常量池中了 ,这一点和下面代码中出现的整形常量(142),浮点型常量(12.1)等的处理是没有区别的。

                   String s="Hello world";
                    int intData=142;
                    double dblData=12.1;


(2) Java虚拟机运行class文件
      当Java虚拟机需要运行一个class文件时,它首先会用类装载器装载进class文件。当然也就需要在内存中存放许多东西。比如class的二进制字节码。还有需要存储class文件中得到的其他信息,比如程序创建的对象,传递给方法的参数,返回值,局部变量等等。怎么多麻烦的数据当然需要管理,JVM会把这些东西都组织到几个“运行时数据区 ”中。这些数据区中就有我们动不动就谈到的"堆"呀,"栈"呀什么的?想要详细了解这部分东西可以看《深入Java虚拟机》第二版第5章。
   
      在这里我只谈谈“方法区 ”这个运行时数据区。在Java虚拟机中,关于被装载类型的信息会在一个逻辑上被称为"方法区"的内存中,当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入该文件。紧接着虚拟机提取其中的类型信息,并将这些信息存储到方法区中。
   
      方法区中的这些类型信息是很有用的,比如:这个类型的全限定名(net.single.HelloWorld);这个类型的直接超类的全限定名;这个类型是类类型还是接口类型;这个类型的访问修饰符(public,final,static)等。还有两个大家都很熟悉的引用:指向类ClassLoader的引用和指向Class类的引用。这是Java反射机制能够运行的关键所在。这里我们要提到的是一个非常重要的信息——该类型的常量池

    上面提到的,class文件结构中的常量池二进制流就被JVM存储在方法区中进行管理。当程序运行时需要使用到常量值的时候,直接在方法区常量池所在的内存中寻找就可以了。

 

(3) 操作码助忆符指令集
     将String s=new String("Hello world");编译成class文件后的指令(由eclipse打开class文件查看的):

0  new java.lang.String [15]  
3  dup
4  ldc <String "Hello word"> [17] 
6  invokespecial java.lang.String(java.lang.String) [19]
9  astore_1 [s]
10  retur

    下面通俗的解释一下这些指令,详细见《深入Java虚拟机》第二版附表:按操作码助忆符排列的指令集。
    ★ new指令: 在内存的堆区域中为新字符串对象分配足够大的空间,并将对象的实例变量设为默认值。
    ★ ldc指令:在内存的方法区常量池中找到String类型字面值常量表 的入口,然后定位到的"Hello word"所在内存中的位置。
    ★ invokespecial指令:调用指定的类构造器(这里调用的是String(String)这一个构造器。将ldc指令所找到的"Hello word"的内容传入到new指令所开辟在堆中的字符串对象中。
    ★ astore_1:将new指令所开辟堆的内存位置存入局部变量s中

 

    将String s="Hello world";编译成class文件后的指令:

0  ldc <String "Hello world"> [15]
2  astore_1 [str]
3  return

    ★ ldc指令:在内存的方法区常量池中找到String类型字面值常量表 的入口,然后定位到的"Hello word"所在内存中的位置(如果常量池中没有"Hello word",则会在其中添加一个"Hello word")。
    ★ astore_1:将ldc指令定位到的常量池中的位置存入局部变量s中

 

镜头总结: String类型脱光了其实也很普通。真正让她神秘的原因就在于String类型字面值常量表 的存在。

 

相关问题解决 

   (问题1) 代码1                                                           代码2

              String sa=new String("Hello world");          String sc="Hello world";
              String sb=new String("Hello world");          String sd="Hello world";
              System.out.println(sa==sb);  // false          System.out.println(sc==sd);  // true
              变量sa,sb中存储的内容是JVM在堆中开辟的两个String对象的内存地址。==比较就是sa,sb变量存储的内容,也就是两个不同的内存地址,当然是false;
              变量sc,sd中存储的内容也是地址,但却都是方法区常量池中"Hello word"所在的地址,自然一样。

   (问题2) 代码1                                                          代码2
              String sa = "ab";                                        String sc="ab"+"cd";
              String sb = "cd";                                        String sd="abcd";
              String sab=sa+sb;                                     System.out.println(sc==sd); //true
              String s="abcd";
              System.out.println(sab==s); // false
              代码1中sa+sb被编译以后使用的是StringBuilder.append(String)方法。JVM会在堆中创建一个StringBuilder类,将sa所指向常量池中的内容"ab"传入,然后调用append(sb所指向的常量池内容)完成字符串合并功能,最后将堆中StringBuilder对象的地址赋给变量sab。而s存储的是常量池中"abcd"的地址。sab与s地址当然不一样了。
              代码2中"ab"+"cd"会直接在编译阶段就合并成常量"abcd",所以相同的字符串在常量池中的地址也相同了。

 

0 请登录后投票
   发表时间:2009-11-29  
insideJVM 是一本好书 。
指正楼主:
ldc指令 的作用是把常量压入栈
invokespecial指令 是弹出栈顶对象,然后执行出栈对象的指定方法
0 请登录后投票
   发表时间:2009-11-30  
呵呵 楼主扒得好光啊!
学习啦
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics