我来我网
https://5come5.cn
 
您尚未 登录  注册 | 菠菜 | 软件站 | 音乐站 | 邮箱1 | 邮箱2 | 风格选择 | 更多 » 
 

本页主题: java中处理字符串 显示签名 | 打印 | 加为IE收藏 | 收藏主题 | 上一主题 | 下一主题

独飞の孤心



性别: 帅哥 状态: 该用户目前不在线
头衔: 孽缘!
等级: 荣誉会员
家族: 单身贵族
发贴: 4484
威望: 3
浮云: 496
在线等级:
注册时间: 2005-10-12
最后登陆: 2011-09-23

5come5帮你背单词 [ fairy /'fZəri/ n. 小妖精,仙女 ]


java中处理字符串

版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://zhangjunhd.blog.51cto.com/blog/113473/17690


分析一下Java中处理字符串的问题。探讨JVM处理基本数据类型和引用数据类型的机制、String类的equals()方法和intern()方法。
@author:ZJ 06-11-25
Blog: http://zhangjunhd.blog.51cto.com/



1.Java中的基本数据类型
  Java中有2种基本数据类型:基本数据类型(在Java中,boolean、byte、short、int、long、char、float、double这八种是基本数据类型)、引用类型。其中,引用类型包括类类型(含数组)、接口类型。



2.java中栈(stack)与堆(heap)
  在java中内存分为“栈”和“堆”这两种(Stack and Heap).基本数据类型存储在“栈”中,对象引用类型实际存储在“堆”中,在栈中只是保留了引用内存的地址值。



3.基本数据类型在栈中的存储
  基本类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int 类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。
  另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义
int a = 3;
int b = 3;
  编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址, 然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a 与b同时均指向3的情况。
  特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有 4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。



4.关于String str = "abc"的内部工作
  Java内部将此语句转化为以下几个步骤:
①先定义一个名为str的对String类的对象引用变量:String str;
②在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o 的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。
③将str指向对象o的地址。
  值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!
  为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。

String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
  注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。
  再看以下代码:

String str1 = "abc";
String str2 = "a"+"bc";
System.out.println(str1==str2); //true

  由上面两段代码结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。
  我们再来更进一步,将以上代码改成:
String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
System.out.println(str1 + "," + str2); //bcd, abc
System.out.println(str1==str2); //false

  这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。
  事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。
  再修改原来代码:
String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
String str3 = str1;

System.out.println(str3); //bcd
String str4 = "bcd";
System.out.println(str1 == str4); //true

  str3 这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。
  我们再接着看以下的代码。
String str1 = new String("abc");
String str2 = "abc";
System.out.println(str1==str2); //false

  创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2); //false

  创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。
  以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。
  这里作如下总结(对后文引入intern()方法有帮助):

String str1 = "abc";               //共享内容值
String str2 = new String("abc");//不共享内容值

  我们从另一个角度来分析上面两句语句,从而引入常量池的概念。常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。

String str1 = "abc";//是字符串常量,它在编译期被确定。
String str2 =new String("abc");
//不是字符串常量,不在编译期确定。
//new String()创建的字符串不放入常量池中。



5.总结Java中处理字符串的机制
  由上面这些例子可发现JVM处理字符串的机制。Java虚拟机会维护一个内部的滞留字符串对象的列表(唯一字符串的池)来避免在堆内存中产生重复的String对象。当JVM从class文件里加载字符串字面量并执行的时候,它会先检查一下当前的字符串是否已经存在于滞留字符串列表,如果已经存在,那就不会再创建一个新的String对象而是将引用指向已经存在的String对象,JVM会在内部为字符串字面量作这种检查,但并不会为通过new关键字创建的String对象作这种检查。当然你可以明确地使用String.intern()方法强制JVM为通过 new关键字创建的String对象作这样的检查。这样可以强制JVM检查内部列表而使用已有的String对象。
  所以结论是,JVM会内在地为字符串字面量维护一些唯一的String对象,程序员不需要为字符串字面量而发愁,但是可能会被一些通过 new关键字创建的String对象而困扰,不过他们可以使用intern()方法来避免在堆内存上创建重复的String对象来改善Java的运行性能。



6.Java中处理字符串的相关方法

6.1 Java.lang.Object对象的equals()源代码
Public boolean equals(Object obj){
Return (this= =obj);
}





  显然,当两个变量指向同一个对象时,equals()方法返回true。



6.2 String.equals()的代码
public boolean equals(Object anObject) {
      if (this == anObject) {
          return true;
      }
      if (anObject instanceof String) {
          String anotherString = (String)anObject;
          int n = count;
          if (n == anotherString.count) {
              char v1[] = value;
              char v2[] = anotherString.value;
              int i = offset;
              int j = anotherString.offset;
              while (n-- != 0) {
                if (v1[i++] != v2[j++])
                    return false;
              }
          return true;
          }
      }
      return false;
  }





  由此,可发现String.equals() 方法比较二者的内容,是一个个的比较的。它不同与java.lang.Object的equals()方法,它仅仅比较两个对象的引用。



6.3 String的intern()方法
  存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;看下面代码:

String s0= “abc”;
String s1=new String(”abc”);
String s2=new String(“abc”);

System.out.println( s0==s1 );

s1.intern();
s2=s2.intern(); //把常量池中“abc”的引用赋给s2

System.out.println( s0==s1);
System.out.println( s0==s1.intern() );
System.out.println( s0==s2 );

  结果为:
false    
false     //虽然执行了s1.intern(),但它的返回值没有赋给s1
true     //说明s1.intern()返回的是常量池中”abc”的引用
true     ////说明s2.intern()返回的是常量池中”abc”的引用

  回到前文引出常量池定义的部分:
String str1 = "abc";//是字符串常量,它在编译期被确定。
String str2 =new String("abc");
//不是字符串常量,不在编译期确定。
//new String()创建的字符串不放入常量池中。
  看下面代码:

String s1=new String("abc");
String s2=s1.intern();

System.out.println( s1==s1.intern() );
System.out.println( s1+" "+s2 );
System.out.println( s2==s1.intern() );

  结果:
false   //说明原来的“abc”仍然存在
abc abc
true   // s2现在为常量池中“abc”的地址,所以有s2==s1.intern()为true

  当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。这是Java api文档中关于intern 方法的定义。
  回到上面的代码示例。在这个类中一开始,我们没有声名一个”abc”常量,所以常量池中一开始是没有”abc”。当我们调用s1.intern()后就在常量池中新添加了一个”abc”常量,原来的不在常量池中的”abc”,即s1(表现为存储地址)仍然存在,所以s1==s1.intern()返回flase。


[ 此贴被zc1984在2007-03-22 22:44重新编辑 ]
本帖最近评分记录:
  • 浮云:10 (by zc1984) | 理由: 优秀转贴,顺便帮你把“字符串”处理了一下,除掉了里面的¥%……&……,哇咔咔
  • 顶端 Posted: 2007-03-22 22:37 | [楼 主]
    我来我网·5come5 Forum » 程序员之家

    Total 0.008229(s) query 4, Time now is:11-23 15:28, Gzip enabled
    Powered by PHPWind v5.3, Localized by 5come5 Tech Team, 黔ICP备16009856号