Java核心技术面试精讲
杨晓峰
前Oracle首席工程师
立即订阅
43147 人已学习
课程目录
已完结 43 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 以面试题为切入点,有效提升你的Java内功
免费
模块一 Java基础 (14讲)
第1讲 | 谈谈你对Java平台的理解?
第2讲 | Exception和Error有什么区别?
第3讲 | 谈谈final、finally、 finalize有什么不同?
第4讲 | 强引用、软引用、弱引用、幻象引用有什么区别?
第5讲 | String、StringBuffer、StringBuilder有什么区别?
第6讲 | 动态代理是基于什么原理?
第7讲 | int和Integer有什么区别?
第8讲 | 对比Vector、ArrayList、LinkedList有何区别?
第9讲 | 对比Hashtable、HashMap、TreeMap有什么不同?
第10讲 | 如何保证集合是线程安全的? ConcurrentHashMap如何实现高效地线程安全?
第11讲 | Java提供了哪些IO方式? NIO如何实现多路复用?
第12讲 | Java有几种文件拷贝方式?哪一种最高效?
第13讲 | 谈谈接口和抽象类有什么区别?
第14讲 | 谈谈你知道的设计模式?
模块二 Java进阶 (16讲)
第15讲 | synchronized和ReentrantLock有什么区别呢?
第16讲 | synchronized底层如何实现?什么是锁的升级、降级?
第17讲 | 一个线程两次调用start()方法会出现什么情况?
第18讲 | 什么情况下Java程序会产生死锁?如何定位、修复?
第19讲 | Java并发包提供了哪些并发工具类?
第20讲 | 并发包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别?
第21讲 | Java并发类库提供的线程池有哪几种? 分别有什么特点?
第22讲 | AtomicInteger底层实现原理是什么?如何在自己的产品代码中应用CAS操作?
第23讲 | 请介绍类加载过程,什么是双亲委派模型?
第24讲 | 有哪些方法可以在运行时动态生成一个Java类?
第25讲 | 谈谈JVM内存区域的划分,哪些区域可能发生OutOfMemoryError?
第26讲 | 如何监控和诊断JVM堆内和堆外内存使用?
第27讲 | Java常见的垃圾收集器有哪些?
第28讲 | 谈谈你的GC调优思路?
第29讲 | Java内存模型中的happen-before是什么?
第30讲 | Java程序运行在Docker等容器环境有哪些新问题?
模块三 Java安全基础 (2讲)
第31讲 | 你了解Java应用开发中的注入攻击吗?
第32讲 | 如何写出安全的Java代码?
模块四 Java性能基础 (3讲)
第33讲 | 后台服务出现明显“变慢”,谈谈你的诊断思路?
第34讲 | 有人说“Lambda能让Java程序慢30倍”,你怎么看?
第35讲 | JVM优化Java代码时都做了什么?
模块5 Java应用开发扩展 (4讲)
第36讲 | 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?
第37讲 | 谈谈Spring Bean的生命周期和作用域?
第38讲 | 对比Java标准NIO类库,你知道Netty是如何实现更高性能的吗?
第39讲 | 谈谈常用的分布式ID的设计方案?Snowflake是否受冬令时切换影响?
周末福利 (2讲)
周末福利 | 谈谈我对Java学习和面试的看法
周末福利 | 一份Java工程师必读书单
结束语 (1讲)
结束语 | 技术没有终点
Java核心技术面试精讲
登录|注册

第5讲 | String、StringBuffer、StringBuilder有什么区别?

杨晓峰 2018-05-15
今天我会聊聊日常使用的字符串,别看它似乎很简单,但其实字符串几乎在所有编程语言里都是个特殊的存在,因为不管是数量还是体积,字符串都是大多数应用中的重要组成。
今天我要问你的问题是,理解 Java 的字符串,String、StringBuffer、StringBuilder 有什么区别?

典型回答

String 是 Java 语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。它是典型的 Immutable 类,被声明成为 final class,所有属性也都是 final 的。也由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的 String 对象。由于字符串操作的普遍性,所以相关操作的效率往往对应用性能有明显影响。
StringBuffer 是为解决上面提到拼接产生太多中间对象的问题而提供的一个类,我们可以用 append 或者 add 方法,把字符串添加到已有序列的末尾或者指定位置。StringBuffer 本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销,所以除非有线程安全的需要,不然还是推荐使用它的后继者,也就是 StringBuilder。
StringBuilder 是 Java 1.5 中新增的,在能力上和 StringBuffer 没有本质区别,但是它去掉了线程安全的部分,有效减小了开销,是绝大部分情况下进行字符串拼接的首选。

考点分析

几乎所有的应用开发都离不开操作字符串,理解字符串的设计和实现以及相关工具如拼接类的使用,对写出高质量代码是非常有帮助的。关于这个问题,我前面的回答是一个通常的概要性回答,至少你要知道 String 是 Immutable 的,字符串操作不当可能会产生大量临时字符串,以及线程安全方面的区别。
如果继续深入,面试官可以从各种不同的角度考察,比如可以:
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java核心技术面试精讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(110)

  • Bin 置顶
    jdk1.8中,string是标准的不可变类,但其hash值没有用final修饰,其hash值计算是在第一次调用hashcode方法时计算,但方法没有加锁,变量也没用volatile关键字修饰就无法保证其可见性。当有多个线程调用的时候,hash值可能会被计算多次,虽然结果是一样的,但jdk的作者为什么不将其优化一下呢?

    作者回复: 这些“优化”在通用场景可能变成持续的成本,volatile read是有明显开销的;
    如果冲突并不多见,read才是更普遍的,简单的cache是更高效的

    2018-05-16
    7
    45
  • 公号-代码荣耀
    今日String/StringBuffer/StringBuilder心得:


    1 String

    (1) String的创建机理
    由于String在Java世界中使用过于频繁,Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池。其运行机制是:创建一个字符串时,首先检查池中是否有值相同的字符串对象,如果有则不需要创建直接从池中刚查找到的对象引用;如果没有则新建字符串对象,返回对象引用,并且将新创建的对象放入池中。但是,通过new方法创建的String对象是不检查字符串池的,而是直接在堆区或栈区创建一个新的对象,也不会把对象放入池中。上述原则只适用于通过直接量给String对象引用赋值的情况。

    举例:String str1 = "123"; //通过直接量赋值方式,放入字符串常量池
    String str2 = new String(“123”);//通过new方式赋值方式,不放入字符串常量池

    注意:String提供了inter()方法。调用该方法时,如果常量池中包括了一个等于此String对象的字符串(由equals方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并且返回此池中对象的引用。


    (2) String的特性
    [A] 不可变。是指String对象一旦生成,则不能再对它进行改变。不可变的主要作用在于当一个对象需要被多线程共享,并且访问频繁时,可以省略同步和锁等待的时间,从而大幅度提高系统性能。不可变模式是一个可以提高多线程程序的性能,降低多线程程序复杂度的设计模式。

    [B] 针对常量池的优化。当2个String对象拥有相同的值时,他们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。

    2 StringBuffer/StringBuilder

    StringBuffer和StringBuilder都实现了AbstractStringBuilder抽象类,拥有几乎一致对外提供的调用接口;其底层在内存中的存储方式与String相同,都是以一个有序的字符序列(char类型的数组)进行存储,不同点是StringBuffer/StringBuilder对象的值是可以改变的,并且值改变以后,对象引用不会发生改变;两者对象在构造过程中,首先按照默认大小申请一个字符数组,由于会不断加入新数据,当超过默认大小后,会创建一个更大的数组,并将原先的数组内容复制过来,再丢弃旧的数组。因此,对于较大对象的扩容会涉及大量的内存复制操作,如果能够预先评估大小,可提升性能。

    唯一需要注意的是:StringBuffer是线程安全的,但是StringBuilder是线程不安全的。可参看Java标准类库的源代码,StringBuffer类中方法定义前面都会有synchronize关键字。为此,StringBuffer的性能要远低于StringBuilder。


    3 应用场景

    [A]在字符串内容不经常发生变化的业务场景优先使用String类。例如:常量声明、少量的字符串拼接操作等。如果有大量的字符串内容拼接,避免使用String与String之间的“+”操作,因为这样会产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)。

    [B]在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程环境下,建议使用StringBuffer,例如XML解析、HTTP参数解析与封装。

    [C]在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程环境下,建议使用StringBuilder,例如SQL语句拼装、JSON封装等。

    作者回复: 很到位

    2018-05-15
    7
    346
  • Hidden
    公司没有技术氛围,项目也只是 功能实现就好,不涉及优化,技术也只是传统技术,想离职,但又怕裸辞后的各种压力
    2018-05-16
    3
    167
  • sea季陪我去看海
    作者我有个疑问,String myStr = "aa" + "bb" + "cc" + "dd"; 应该编译的时候就确定了,不会用到StringBuilder。理由是:
    String myStr = "aa" + "bb" + "cc" + "dd";
    String h =aabbccdd
    Mystr ==h 上机实测返回的是true,如果按照你的说法,应该是返回false才对,因为你说拼接用到stringbuilder,那mystr应该是堆地址,h是常亮池地址。
    2018-05-16
    4
    66
  • Kongk0ng
    编译器为什么不把
    String myStr = "aa" + "bb" + "cc" + "dd";
    默认优化成
    String myStr = "aabbccdd";
    这样不是更聪明嘛
    2018-05-16
    3
    36
  • Jerry银银
    要完全消化一篇文章的所有内容,真得不是一天就能搞定的,可能需要一个月,甚至好几个月。就比如今天的字符串,我觉得这个话题覆盖的面太广:算法角度;易用角度;性能角度;编码传输角度等

    但是好在,我获得见识。接下来,花时间慢慢研究呗,连大师们都花了那么多时间研究,我们多花点时间,很正常嘛😄

    一点学习心得,和大家分享
    2018-05-15
    35
  • 愉悦在花香的日子里
    getBytes和String相关的转换时根据业务需要建议指定编码方式,如果不指定则看看JVM参数里有没有指定file.encoding参数,如果JVM没有指定,那使用的默认编码就是运行的操作系统环境的编码了,那这个编码就变得不确定了。常见的编码iso8859-1是单字节编码,UTF-8是变长的编码。

    作者回复: 不错,莫依赖于不确定因素

    2018-05-15
    32
  • 明翼
    回答一下上面一个人的问题,问题是“”String s3 = new String("12") + new String("34");
    s3.intern();
    String s4 = "1234";
    System.out.println(s3 == s4);//true

    求解,为什么在第二段比较中会返回true,从字节码看s3应该就是生成了一个stringbuilder对象完成连接操作后执行了toString, s3不是应该仍然是堆内的对象地址吗?为什么会和常量池中的地址相等?“”



    我之前也是不明白s3为什么等于s4,查了下资料,说是在jdk1.7之后,如果字符串在堆中有实例,那intern方法就会把这个字符串的引用放在字符串常量池里,所以,String s3 = new String("12") + new String("34");这里在字符串常量池里放了一个字符串“12”,一个字符串“34”,在堆里存放他们的运算结果“1234”,然后把“1234”的引用返回给s3,
    s3.intern()这段代码运行时,jvm在堆里先到了字符串“1234”,所以就会把他的引用放到字符串常量池里,这个引用和s3相等,
    String s4 = "1234";这个代码时,会把字符串常量池里“1234”的引用返回给s4,所以s3是等于s4的,


    个人理解,如有不对,请指正,谢谢😜
    2018-06-25
    1
    27
  • Van
    String myStr = "aa" +"bb" + "cc" +"dd";反编译后并不会用到StringBuilder,老师反编译结果中出现StringBuilder是因为输出中拼接了字符串System.out.println("My String:" + myStr);

    作者回复: 嗯,文中的例子有歧义,确实欠考虑

    2018-09-19
    21
  • DoctorDeng
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
      这道面试题不错,即考察了 intern() 的用法,也考察了字符串常量池在不同版本 JDK 的实际存储,具体可以看看美团博客:https://tech.meituan.com/in_depth_understanding_string_intern.html,

    作者回复: 思路比结论更有价值

    2018-09-21
    20
  • Jerry银银
    特别喜欢这句话:“仅仅是字符串一个实现,就需要 Java 平台工程师和科学家付出如此大且默默无闻的努力,我们得到的很多便利都是来源于此。”

    我想说,同学们,写代码的时候记得感恩哦😄

    对于字符串的研究,我觉得能很好的理解计算机的本质和训练计算机思维,提升自己解决问题的能力。

    小小的字符串有着诸多巨人的影子

    作者回复: 非常感谢

    2018-05-15
    20
  • 王万云
    看大神的文章真的提高太多了,而且还要看评论,评论区也都是高手云集
    2018-05-20
    18
  • a man is a man
    1.通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
    2.JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与1.7之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。jdk1.7之后,常量池不仅仅可以存储对象,还可以存储对象的引用
    3.在jdk1.7之前,字符串常量存储在方法区的PermGen Space(永久代)。在jdk1.7之后,字符串常量重新被移到了堆中。
    2018-10-10
    14
  • 肖一林
    这篇文章写的不错,由浅入深,把来龙去脉写清楚了

    作者回复: 谢谢认可

    2018-05-15
    13
  • 毛荣荣
    首先要明白,Object obj = new Object();
    obj是对象的引用,它位于栈中, new Object() 才是对象,它位于堆中
    举例:String str1 = "abc"; //通过直接量赋值方式,放入字符串常量池
    String str2 = new String(“abc”);//通过new方式赋值方式,不放入字符串常量池
    String str1 = new String("abc");
    Stirng str2 = "abc";
    虽然两个语句都是返回一个String对象的引用,但是jvm对两者的处理方式是不一样的。
    对于第一种,jvm会马上在heap中创建一个String对象,然后将该对象的引用返回给用户。
    对于第二种,jvm首先会在内部维护的strings pool中通过String的 equels 方法查找是对象池中是否存放有该String对象,如果有,则返回已有的String对象给用户,
    而不会在heap中重新创建一个新的String对象;如果对象池中没有该String对象,jvm则在heap中创建新的String对象,将其引用返回给用户,同时将该引用添加至strings pool中。
    注意:使用第一种方法创建对象时,jvm是不会主动把该对象放到strings pool里面的,除非程序调用 String的intern方法。

    看下面的代码:
            if(a == b){
                System.out.println("a == b");
            } else {
                System.out.println("a != b");
            }
            //
            if(a == c){
                System.out.println("a == c");
            } else {
                System.out.println("a != c");
            }

            System.out.println("a.equals(b):" + a.equals(b));
            System.out.println("a.equals(c):" + a.equals(c));
            //
            if(a == c){
                System.out.println("a == c");
            } else {
                System.out.println("a != c");
            }

            System.out.println("a.equals(b):" + a.equals(b));
            System.out.println("a.equals(c):" + a.equals(c));

    // 输出结果为:
    a == b
    a != c
    a.equals(b):true
    a.equals(c):true

    2018-10-25
    12
  • jamie

    编译器为什么不把
    String myStr = "aa" + "bb" + "cc" + "dd";
    默认优化成
    String myStr = "aabbccdd";
    这样不是更聪明嘛

    这个我反编译试过,已经优化成aabbccdd了
    2018-06-14
    11
  • X
    看了评论中的,感觉误区还是不少的,这里经过心爱之人极大的帮助,总结如下,
    首先要搞清楚一个概念,就是到底创建了几个对象。基于1.8
    String s1=new String("11");//在堆中创建对象,并且新建一个"11"对象放入常量池

    对比声明式拼接
    String s1=new String("11")+new String("111");//创建5个,分别是堆中的3个:"11" "111" "11111",常量池2个 "11"
    "111"

    看到不同了么,很多人认为,new出来的只在堆里,这就是最大的错误

    我们再说常量池,早知道,常量池里不止是字面量常量,还有引用型常量,这是重点


    intern这个方法是这样说的,如果该对象代表的字符串在常量池可以找到,那么,返回该常量的引用,如果找不到,那么,去堆中查找,堆中存在,则讲堆中对象的引用放入常量池,并返回该引用(看到了么,是将堆中对象的引用放入常量池),如果都没有找到,则讲该字面量放入常量池

    如果大家有兴趣,可以尝试一下,结合不同的创建方式,来调用intern,进行对比
    2019-04-23
    9
  • фщэьшух
    回答上面的问题,问题如下:
    作者我有个疑问,String myStr = "aa" + "bb" + "cc" + "dd"; 应该编译的时候就确定了,不会用到StringBuilder。理由是:
    String myStr = "aa" + "bb" + "cc" + "dd";
    String h =aabbccdd
    Mystr ==h 上机实测返回的是true,如果按照你的说法,应该是返回false才对,因为你说拼接用到stringbuilder,那mystr应该是堆地址,h是常亮池地址。
    String myStr = "aa" + "bb" + "cc" + "dd"; 应该编译的时候就确定了,不会用到StringBuilder。这句话是对的,在编译阶段就直接合成aaccccdd,然后看常量池里有没有,有也直接引用,没有就在常量池中生成,返回引用。
    那么此时String h =aabbccdd,则就引用了常量池中的,必然返回true
    2018-08-25
    8
  • 轩尼诗。
    String s = new String(“abc”) 创建了几个对象?
    答案1:在字符串常量池中查找有没有“abc”,有则作为参数,就是创建了一个对象;没有则在常量池中创建,然后返回引用,那就是创建了两个对象。
    答案2:直接在堆中创建一个新的对象。不检查字符串常量池,也不会把对象放入池中。
    网上正确答案貌似是两个,求指教到底是哪个!


    第一种可以写成String a = "abc";String s = new String(a) ,那么第一种解释说得通,String a = "abc"会在常量池创建“abc”。但是string类的intern()是在字符串常量池中查找该字符串,有就返回,没有就创建,如果第一种解释说得通,那这个方法就废了。
    2018-05-31
    8
  • echo
    String是immutable,在security, Cache,Thread Safe等方面都有很好的体现。
    Security: 传参的时候我们很多地方使用String参数,可以保证参数不会被改变,比如数据库连接参数url等,从而保证数据库连接安全。
    Cache: 因为创建String前先去Constant Pool里面查看是否已经存在此字符串,如果已经存在,就把该字符串的地址引用赋给字符变量;如果没有,则在Constant Pool创建字符串,并将字符串引用赋给字符串变量。所以存在多个引用指向同一个字符串对象,利用缓存有助提高内存开销。
    Thread Safe: 因为String是immutable, 所以它是Automatically thread safe.
    问题:我一直不能很好的理解最后一个体现,到底String是如何体现在thread safe,老师能不能用简短的线程代码给讲讲?非常感激。
    2018-05-16
    8
收起评论
99+
返回
顶部