JVM对字符串变量的处理 .

在Java中,我们经常会用到字符串类型,关于字符串类型,有这么三个类型:String , StringBuffer, StringBuilder,那么为什么一个简单的字符串类型要分为这三种呢?JVM对他们的处理有是怎样的呢?


1)String,不可变的字符串

我们先来看一下最基本的笔试面试题:String javaStr = new String("小学徒的成长历程");这条语句创建了几个字符串对象?

答案是两个,一个是“小学徒的成长历程”这个直接量对应的字符串对象,一个是由new String()构造器返回的字符串对象

那么究竟为什么是两个呢?为什么会有直接量对应的字符串对象呢?好啦,言归正传。其实这个就与JVM对字符串变量的处理有关了。

对于Java程序中的字符直接量(eg:String javaStr = "小学徒的成长历程"),JVM会使用一个字符串池来保存他们,当第一次使用某个字符串直接量时,JVM会将它放入字符串池进行缓存。当程序再次需要使用该字符串时,无须重新创建一个新的字符串,而是直接引用变量执行字符串中已有的字符串。但是对于使用构造器进行初始化的字符串(eg :String javaStr = new String("小学徒的成长历程")),因为凡是通过构造器创建的对象都会进行内存分配,所以他就不会指向缓存池中已有的对象而指向新的对象,这样就会造成缓存池中存在多个值相同的字符串对象,浪费了资源。

复制代码
 1 public class Test{
 2     
 3     public static void main(String[] args) {
 4         //通过构造器进行初始化,如果是第一次,他同样会在缓存池中缓存该字符串
 5         //但是他依旧另外创建一个对象并指向该对象
 6         String newStr = new String("小学徒的成长历程");
 7         //javaStr的值是字符串直接量
 8         //所以,javaStr指向字符串缓存池中的"小学徒的成长历程"字符串
 9         String javaStr = "小学徒的成长历程";
10         //由于缓存池中已经有了"小学徒的成长历程"字符串
11         //所以,anotherStr也指向字符串缓存池中的"小学徒的成长历程"字符串
12         String anotherStr = "小学徒的成长历程";
13     
14         System.out.println("javaStr == anotherStr : " + (javaStr == anotherStr));    //判断两个字符串是不是指向同一个对象
15         System.out.println("newStr == anotherStr  : " + (newStr == anotherStr));
16         System.out.println("newStr == javaStr     : " + (newStr == javaStr));
17     } 
18 }
复制代码

上面的测试代码块执行后,他在内存中的分配情况是这样的:


 

下面我们再看一题经典的笔试面试题:String javaStr = "小学徒" + "的" + "成长历程";总共创建了多少个字符串对象?

答案是一个,因为如果一个字符串连接表达式的值可以在编译时确定下来,那么JVM会在编译时计算该字符串变量的值,并让他指向字符串池中对应的字符串。但如果程序使用了变量,或者调用了方法,那么就只能等到运行时才可确定该字符串连接式的值,也就无法在编译时确定字符串变量的值,因此无法确定该字符串变量的值,所以无法利用JVM的字符串池。

下面我们写一段代码验证一下吧:

复制代码
 1 public class Test{
 2     
 3     public static void main(String[] args) {
 4         String anotherStr = "小学徒的成长历程";
 5         
 6         //虽然javaStr的值不是直接量,但是因为javaStr的值可以在编译时确定
 7         //所以javaStr也会直接引用字符串池中对应的字符串
 8         String javaStr = "小学徒" + "的" + "成长历程";
 9         
10         String a = "的";
11         
12         //使用了变量,只能等到运行时才可确定该字符串连接式的值
13         //也就无法在编译时确定字符串变量的值,因此无法确定该字符串变量的值,所以无法利用JVM的字符串池
14         String contactStr = "小学徒" + a + "成长历程";
15         
16         //调用了方法只能等到运行时才可确定该字符串连接式的值
17         //也就无法在编译时确定字符串变量的值,因此无法确定该字符串变量的值,所以无法利用JVM的字符串池
18         String methodStr =  "小学徒的成长历程" + a.length();
19         
20         //判断各个字符串是否相等
21         System.out.println("javaStr == anotherStr : " + (javaStr == anotherStr));
22         System.out.println("contactStr == javaStr : " + (contactStr == javaStr));
23         System.out.println(" methodStr == javaStr : " + (methodStr == javaStr));
24         
25     
26     } 
27 }
复制代码


③呵呵,我们再用一题经典面试笔试题目来抛砖引玉吧,这样比较可以诱导大家的思考,同时增加大家的兴趣,不会太过闷,而且还能提醒大家在笔试面试的时候该注意什么地方,好啦,言归正传。String name = "小学徒";  name = name + "的成长空间";两条语句总共创建了多少个字符串对象?

答案是两个,因为当一个String对象创建完成后,该String类里包含的字符串序列就被固定下来了,以后永远都不能改变。(如果目前不懂这句的话,没关系,看下补充你就理解的了)

复制代码
1 public class Test{
2     
3     public static void main(String[] args) {
4         String name = "小学徒";    //定义一个字符串变量
5         System.out.println(System.identityHashCode(name));    //输出该对象的hashCode值
6         name = name + "的成长空间"; //拼接字符串变量
7         System.out.println(System.identityHashCode(name));//输出该对象的hashCode值
8     } 
9 }
复制代码


我们可以看到两个的值是不一样的,所以此处说明String是典型的不可变类。

或许你看了之后会说,没关系啊,这个java会自动进行垃圾回收,到时候回收就行了,到这里,我就得补充一下前面没有说到的问题了:

java为了节省内存,提高资源的复用,才引入了字符串缓存池的概念,而且,在缓存池中的字符串是不会被垃圾回收机制回收的,基本都是常驻内存,所以过多使用String类,可能会出现内存溢出

所以前面的代码中,对String对象进行操作后,其返回的是一个新的对象,之前那个对象是没有改变的,改变的是name这个引用所指的对象,这时候的对象已经是新的对象,然而之前那个对象被废弃了,但是他存在缓存池,因此不会被垃圾回收机制回收,所以这里会容易出现内存泄漏,所以如果要操作字符串,尽量不用String而改为使用StringBuffer或者StringBuilder。


2)StringBuilder和StringBuffer:可变的字符串

之所以说他们会改变的原因是:StringBuilder和StringBuffer在进行字符串操作的时候就不会去创建一个新出现的对象,引用的都是同一个对象,减少了String带来的弊端。

复制代码
1 public class Test{
2     
3     public static void main(String[] args) {
4         StringBuilder sb = new StringBuilder("小学徒");
5         System.out.println(System.identityHashCode(sb));
6         sb.append("的成长历程");
7         System.out.println(System.identityHashCode(sb));
8     }
9 }
复制代码

那么StringBuilder和StringBuffer这两个类有什么区别呢?

他们之间的唯一区别就在于StringBuffer是线程安全的,也就是说StringBuffer类里绝大部分方法都增加了synchronized修饰符,这样就降低了该方法的执行效率,所以在没有多线程的环境下,推荐使用StringBuilder。

 StringBuffer的源代码:


StringBuilder的源代码:

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值