String拓展:String和StringBuilder、StringBuffer的区别?

本文详细讲解了Java中不可变String的原理及其在内存消耗上的局限,介绍了可变字符序列StringBuilder的使用及其优势,通过实例和代码对比展示了两者在效率上的显著差异,适合理解String不可变性并提升代码性能的开发者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上一篇文章中,我们学习了String类中的常用方法,不知道大家是否留意到我反复提到的一句话:修改和截取时,返回的都是新的字符串,原先的字符串并没有改变。

那么这篇文章,我们就来看一下我为什么反复的提这句话。当然,如果没有看的同学可以先看一下我的上一篇文章:Java基础总结(六):String类中的常用方法

从源码分析String为什么不可变:

首先,我们知道,String的本质是不可变字符序列,听名字就知道了哈,他本身就是没有办法更改的,我们要对他做一些改变的话,只能创建一个新的字符串,而不能修改它本身。我们通过源码来分析一下他为什么不能被更改:

可以看到,String的本质其实是一个Char类型的数组,在他的源码中我们可以清楚的看到这个数组前面使用final进行了修饰,我们知道一旦被final修饰,就表明他不再能够被更改了。所以,String是不可变的字符序列。

使用String的弊端和改进方法:

        那么有人就说了,不对啊,我不止一次的更改过字符串啊,比如我们常见的拼串,不就是改变了字符串吗?其实我们只是创建了新的字符串而已,原先的字符串并没有改变。

        这样做有一个弊端,就是我们每操作字符串一次,就会生成一个新的字符串对象,也就会占据一个对象的内存空间,这会造成内存空间的浪费,当然,我们现在的电脑内存已经很大了,多几个字符串的内存空间不算什么,但不要忘了,我们目前的程序也好,项目也好,大多都是自己运行,自己使用,不会出现多用户的操作。但如果像企业中开发的那些项目,同时几千几万甚至几十万人在线操作,举一个很简单的例子:

        王者荣耀这款游戏的日活跃用户最高达到了1亿以上,如果大家都更改自己的游戏昵称,在使用String的情况下,更改昵称会产生大量的新字符串,这对服务器是一个很大的考验。

        那么怎样避免这种情况发生呢?这就需要用到我们的可变字符序列:StringBuilder和StringBuffer了。

StringBuilder和StringBuffer:

他俩都是可变字符序列,不同的是:StringBuffer实现了线程安全,需要做线程同步检查,StringBuilder不实现线程安全,不用做线程同步的检查。相比之下StringBuilder的效率就会略高一些,现阶段我们没有开发多线程的项目,所以一般采用StringBuilder。

通过源码了解StringBuilder和StringBuffer:

首先我们同过他们的源码了解一下他们,顺便提一下,通过源码学习是一种很好的习惯,能够帮助我们更好的理解一些工具类的工作原理,同时我们也可以学习开发者高效的代码结构和逻辑,这在无形中就会提高我们自己的开发效率。在IDEA中,我们按住Ctrl键,点击代码中的String即可阅读String类的源码啦。

StringBulider源码:

 父类构造方法:

 首先我们可以看到,StringBuilder继承自AbstractStringBuilder,同时它的构造方法中调用了父类的构造方法,在父类构造方法中可以看到,他的本质依然是一个char类型的数组,不同的是前面不再有final修饰,也就代表它是可以改变的。

StringBuffer源码:

 

 我们可以发现,StringBuffer同样继承自AbstractStringBuilder,这也就表明了它和StringBuilder的方法是完全相同的,不同的是,在StringBuffer的方法前都有synchronized关键字修饰,这就是线程同步的关键字,表明StringBuffer需要做线程同步检查。

了解了源码之后,我们再来学习一下StringBuilder中的一些常用方法(由于StringBuffer和StringBuilder方法相同,我们只了解其中一个即可):

append(String str)在原先字符串后追加str
insert(int index,String str)在原先字符串index的位置插入str
delete(int beginindex,int endsindex)删除原先字符串中beginindex到endsindex位置的字符
reverse()将原先字符串逆向排列

下面使用代码演示:

        StringBuffer sb1=new StringBuffer();
        sb1.append("我爱我的祖国");
        System.out.println("初始可变字符序列:"+sb1);

        //追加“,祖国河山大好。”
        sb1.append(",祖国河山大好");
        System.out.println("追加后的字符串:"+sb1);

        //删除0到2的字符
        sb1.delete(0,2);
        System.out.println("删除后的字符串:"+sb1);

        //插入“美丽”
        sb1.insert(1,"美丽");
        System.out.println("插入后的字符串:"+sb1);

        //将字符串逆序
        sb1.reverse();
        System.out.println("逆序后的字符串:"+sb1);

运行结果:

 可以看到,从始至终我们都是对sb1这个字符串在操作,并没有新的字符串生成,这就是可变字符串好用的地方。

用代码比较String和StringBuilder所占用的内存和效率:

接下来我们使用代码来看看,在大量更改字符串时,String和StringBuilder效率的对比:

注释我都写在代码里了:

 //String 与StringBulider 效率对比:
        System.out.println("首先使用String======================================");
        //创建字符串
        String str1="";
        
        //返回JVM当前剩余的内存空间,单位为字节
        long num1= Runtime.getRuntime().freeMemory();
        //返回当前的时间
        long time1=System.currentTimeMillis();
        //对str1进行10000次更改
        for(int i=0;i<10000;i++){
            str1+=i;//str1=str1+1
        }
        
        //返回大量更改字符串工作完成后的时间和剩余内存
        long num2=Runtime.getRuntime().freeMemory();
        long time2=System.currentTimeMillis();
        
        //用结束时间和内存减去开始前的时间和内存,就是完成工作所消耗的时间和内存
        System.out.println("String占用的内存:"+(num2-num1));
        System.out.println("String占用的时间:"+(time2-time1));
        System.out.println("下面使用StringBuilder===============================");
        StringBuilder sb=new StringBuilder();
        //返回JVM剩余的内存空间,单位为字节
        long num3= Runtime.getRuntime().freeMemory();
//        返回当前时间,注意单位是毫秒
        long time3=System.currentTimeMillis();
        for(int i=0;i<10000;i++){
            sb.append(i);
        }
        //同样返回结束时的时间和内存,与开始前的时间和内存相减即可得到占用的时间和内存
        long num4=Runtime.getRuntime().freeMemory();
        long time4=System.currentTimeMillis();
        System.out.println("StringBuilder占用的内存:"+(num4-num3));
        System.out.println("StringBuilder占用的时间:"+(time4-time3));

原理就是先获得刚开始的内存和时间,然后获取完成工作后的内存和时间,两个相减即可得到完成工作所占用的时间和内存。我们同样对字符串进行10000次的更改,看看会有什么差别

运行效果:

 可以看到,对比相当的明显,由于String每次都产生新的对象,所以对内存的消耗可以用恐怖来形容,而StringBuilder消耗的内存几乎可以忽略不计,因为从始至终只产生一个对象。消耗时间方面,由于没次都要开辟新的内存空间,所以String更慢。

总结:

这篇文章总结了可变字符序列和不可变字符序列的区别,简单的带大家了解了StringBuilder和StringBuffer,并通过对比直观的感受了二者效率的差异,以往大家能够真正的理解他们的区别。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值