Java开发笔记(十)StringBuffer和StringBuilder的由来

举报
凭栏兔 发表于 2024/10/26 16:13:31 2024/10/26
【摘要】 本来String类已经能够完成字符串操作的所有功能,为何Java又提供了专门的StringBuffer和StringBuilder呢?这要从String类的设计说起了,查看String的源码,发现其内部采用字符数组保存字符串,如下所示: private final char value[];可是问题在于,这个字符数组被final修饰了,意味着数组大小不可改变。若想将现有字符串拼接一段字...

本来String类已经能够完成字符串操作的所有功能,为何Java又提供了专门的StringBuffer和StringBuilder呢?这要从String类的设计说起了,查看String的源码,发现其内部采用字符数组保存字符串,如下所示:

    private final char value[];

可是问题在于,这个字符数组被final修饰了,意味着数组大小不可改变。若想将现有字符串拼接一段字符形成新串(无论是加号拼接还是调用format方法),String类就无法扩充现有的字符数组,只能重新生成新的String对象。然而在需要频繁拼接字符串的场合,不断地重新生成String对象,这涉及到内存数据的反复销毁和反复分配,无疑对程序性能产生巨大的影响。
为了解决频繁拼接带来的性能问题,一开始Java提供了StringBuffer类,其内部的value数组是大小可变的,在拼接字符串时无需重新生成StringBuffer对象,由此避免了对象创建与对象销毁带来的开销。StringBuffer的拼接方法名叫append,因为该方法同时也返回拼接后的StringBuffer对象,所以拼接多个字符串时只需连续调用append即可。
不过Java把StringBuffer类设计成线程安全,它的append方法被synchronized修饰,表示这是个同步方法。既然是同步方法,那么调用之时就会先加锁,等调用结束再解锁。该设计的意图是让StringBuffer类更加健壮,但是拼接字符串几乎不存在多线程并发的场景,绝大多数情况都是按顺序拼接,谁会脑袋抽筋中途插队拼接呢?于是这个锁同步设计形同鸡肋。
鉴于加解锁也会带来额外的性能损耗,因此JDK1.5又增加了StringBuilder类,该类的实现方式与StringBuffer类大同小异,主要区别在于StringBuilder类的append方法去掉了synchronized修饰,从而省去了无谓的加解锁操作。
下面通过实际代码演示一下StringBuffer、StringBuilder和String三者在频繁拼接时候的性能表现,每种对象分别拼接字符串199999次:

    public static void main(String[] argv) {
    	testBuffer(199999);
    	testBuilder(199999);
    	testString(199999);
    }

    // 测试StringBuffer的字符串拼接
    private static void testBuffer(int count) {
        long beginTime = System.currentTimeMillis();
        StringBuffer buffer = new StringBuffer("");
        for (int i=0; i<=count; i++) {
            buffer.append("a");
        }
        long endTime = System.currentTimeMillis();
        String costTime = String.format("%.3fs", (endTime-beginTime)/1000.0);
        System.out.println("testBuffer cost "+costTime);
    }

    // 测试StringBuilder的字符串拼接
    private static void testBuilder(int count) {
        long beginTime = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder("");
        for (int i=0; i<=count; i++) {
            builder.append("a");
        }
        long endTime = System.currentTimeMillis();
        String costTime = String.format("%.3fs", (endTime-beginTime)/1000.0);
        System.out.println("testBuilder cost "+costTime);
    }

    // 测试String的字符串拼接
    private static void testString(int count) {
        long beginTime = System.currentTimeMillis();
        String str = "";
        for (int i=0; i<=count; i++) {
            str += "a";
        }
        long endTime = System.currentTimeMillis();
        String costTime = String.format("%.3fs", (endTime-beginTime)/1000.0);
        System.out.println("testString cost "+costTime);
    }

运行测试代码,观察到如下所示的结果日志:

testBuffer cost 0.022s
testBuilder cost 0.010s
testString cost 13.865s

可见StringBuilder耗时最短,StringBuffer其次当然差距也不大,String耗时最长并且差距显著增大。上述案例说明,在大量拼接字符串的场合,采取StringBuilder实现是最经济的。


更多Java技术文章参见《Java开发笔记(序)章节目录

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。