第5讲 | String、StringBuffer、StringBuilder有什么区别?
下载APP
关闭
渠道合作
推荐作者
第5讲 | String、StringBuffer、StringBuilder有什么区别?
2018-05-15 杨晓峰 来自北京
《Java核心技术面试精讲》
课程介绍
讲述:黄洲君
时长12:00大小5.49M
今天我会聊聊日常使用的字符串,别看它似乎很简单,但其实字符串几乎在所有编程语言里都是个特殊的存在,因为不管是数量还是体积,字符串都是大多数应用中的重要组成。
今天我要问你的问题是,理解 Java 的字符串,String、StringBuffer、StringBuilder 有什么区别?
典型回答
String 是 Java 语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。它是典型的 Immutable 类,被声明成为 final class,所有属性也都是 final 的。也由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的 String 对象。由于字符串操作的普遍性,所以相关操作的效率往往对应用性能有明显影响。
StringBuffer 是为解决上面提到拼接产生太多中间对象的问题而提供的一个类,我们可以用 append 或者 add 方法,把字符串添加到已有序列的末尾或者指定位置。StringBuffer 本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销,所以除非有线程安全的需要,不然还是推荐使用它的后继者,也就是 StringBuilder。
StringBuilder 是 Java 1.5 中新增的,在能力上和 StringBuffer 没有本质区别,但是它去掉了线程安全的部分,有效减小了开销,是绝大部分情况下进行字符串拼接的首选。
考点分析
几乎所有的应用开发都离不开操作字符串,理解字符串的设计和实现以及相关工具如拼接类的使用,对写出高质量代码是非常有帮助的。关于这个问题,我前面的回答是一个通常的概要性回答,至少你要知道 String 是 Immutable 的,字符串操作不当可能会产生大量临时字符串,以及线程安全方面的区别。
如果继续深入,面试官可以从各种不同的角度考察,比如可以:
通过 String 和相关类,考察基本的线程安全设计与实现,各种基础编程实践。
考察 JVM 对象缓存机制的理解以及如何良好地使用。
考察 JVM 优化 Java 代码的一些技巧。
String 相关类的演进,比如 Java 9 中实现的巨大变化。
…
针对上面这几方面,我会在知识扩展部分与你详细聊聊。
知识扩展
1. 字符串设计和实现考量
我在前面介绍过,String 是 Immutable 类的典型实现,原生的保证了基础线程安全,因为你无法对它内部数据进行任何修改,这种便利甚至体现在拷贝构造函数中,由于不可变,Immutable 对象在拷贝时不需要额外复制数据。
我们再来看看 StringBuffer 实现的一些细节,它的线程安全是通过把各种修改数据的方法都加上 synchronized 关键字实现的,非常直白。其实,这种简单粗暴的实现方式,非常适合我们常见的线程安全类实现,不必纠结于 synchronized 性能之类的,有人说“过早优化是万恶之源”,考虑可靠性、正确性和代码可读性才是大多数应用开发最重要的因素。
为了实现修改字符序列的目的,StringBuffer 和 StringBuilder 底层都是利用可修改的(char,JDK 9 以后是 byte)数组,二者都继承了 AbstractStringBuilder,里面包含了基本操作,区别仅在于最终的方法是否加了 synchronized。
另外,这个内部数组应该创建成多大的呢?如果太小,拼接的时候可能要重新创建足够大的数组;如果太大,又会浪费空间。目前的实现是,构建时初始字符串长度加 16(这意味着,如果没有构建对象时输入最初的字符串,那么初始值就是 16)。我们如果确定拼接会发生非常多次,而且大概是可预计的,那么就可以指定合适的大小,避免很多次扩容的开销。扩容会产生多重开销,因为要抛弃原有数组,创建新的(可以简单认为是倍数)数组,还要进行 arraycopy。
前面我讲的这些内容,在具体的代码书写中,应该如何选择呢?
在没有线程安全问题的情况下,全部拼接操作是应该都用 StringBuilder 实现吗?毕竟这样书写的代码,还是要多敲很多字的,可读性也不理想,下面的对比非常明显。
其实,在通常情况下,没有必要过于担心,要相信 Java 还是非常智能的。
我们来做个实验,把下面一段代码,利用不同版本的 JDK 编译,然后再反编译,例如:
先编译再反编译,比如使用不同版本的 JDK:
JDK 8 的输出片段是:
而在 JDK 9 中,反编译的结果就会有点特别了,片段是:
你可以看到,非静态的拼接逻辑在 JDK 8 中会自动被 javac 转换为 StringBuilder 操作;而在 JDK 9 里面,则是体现了思路的变化。Java 9 利用 InvokeDynamic,将字符串拼接的优化与 javac 生成的字节码解耦,假设未来 JVM 增强相关运行时实现,将不需要依赖 javac 的任何修改。
在日常编程中,保证程序的可读性、可维护性,往往比所谓的最优性能更重要,你可以根据实际需求酌情选择具体的编码方式。
2. 字符串缓存
我们粗略统计过,把常见应用进行堆转储(Dump Heap),然后分析对象组成,会发现平均 25% 的对象是字符串,并且其中约半数是重复的。如果能避免创建重复字符串,可以有效降低内存消耗和对象创建开销。
String 在 Java 6 以后提供了 intern() 方法,目的是提示 JVM 把相应字符串缓存起来,以备重复使用。在我们创建字符串对象并调用 intern() 方法的时候,如果已经有缓存的字符串,就会返回缓存里的实例,否则将其缓存起来。一般来说,JVM 会将所有的类似“abc”这样的文本字符串,或者字符串常量之类缓存起来。
看起来很不错是吧?但实际情况估计会让你大跌眼镜。一般使用 Java 6 这种历史版本,并不推荐大量使用 intern,为什么呢?魔鬼存在于细节中,被缓存的字符串是存在所谓 PermGen 里的,也就是臭名昭著的“永久代”,这个空间是很有限的,也基本不会被 FullGC 之外的垃圾收集照顾到。所以,如果使用不当,OOM 就会光顾。
在后续版本中,这个缓存被放置在堆中,这样就极大避免了永久代占满的问题,甚至永久代在 JDK 8 中被 MetaSpace(元数据区)替代了。而且,默认缓存大小也在不断地扩大中,从最初的 1009,到 7u40 以后被修改为 60013。你可以使用下面的参数直接打印具体数字,可以拿自己的 JDK 立刻试验一下。
你也可以使用下面的 JVM 参数手动调整大小,但是绝大部分情况下并不需要调整,除非你确定它的大小已经影响了操作效率。
Intern 是一种显式地排重机制,但是它也有一定的副作用,因为需要开发者写代码时明确调用,一是不方便,每一个都显式调用是非常麻烦的;另外就是我们很难保证效率,应用开发阶段很难清楚地预计字符串的重复情况,有人认为这是一种污染代码的实践。
幸好在 Oracle JDK 8u20 之后,推出了一个新的特性,也就是 G1 GC 下的字符串排重。它是通过将相同数据的字符串指向同一份数据来做到的,是 JVM 底层的改变,并不需要 Java 类库做什么修改。
注意这个功能目前是默认关闭的,你需要使用下面参数开启,并且记得指定使用 G1 GC:
前面说到的几个方面,只是 Java 底层对字符串各种优化的一角,在运行时,字符串的一些基础操作会直接利用 JVM 内部的 Intrinsic 机制,往往运行的就是特殊优化的本地代码,而根本就不是 Java 代码生成的字节码。Intrinsic 可以简单理解为,是一种利用 native 方式 hard-coded 的逻辑,算是一种特别的内联,很多优化还是需要直接使用特定的 CPU 指令,具体可以看相关源码,搜索“string”以查找相关 Intrinsic 定义。当然,你也可以在启动实验应用时,使用下面参数,了解 intrinsic 发生的状态。
可以看出,仅仅是字符串一个实现,就需要 Java 平台工程师和科学家付出如此大且默默无闻的努力,我们得到的很多便利都是来源于此。
我会在专栏后面的 JVM 和性能等主题,详细介绍 JVM 内部优化的一些方法,如果你有兴趣可以再深入学习。即使你不做 JVM 开发或者暂时还没有使用到特别的性能优化,这些知识也能帮助你增加技术深度。
3.String 自身的演化
如果你仔细观察过 Java 的字符串,在历史版本中,它是使用 char 数组来存数据的,这样非常直接。但是 Java 中的 char 是两个 bytes 大小,拉丁语系语言的字符,根本就不需要太宽的 char,这样无区别的实现就造成了一定的浪费。密度是编程语言平台永恒的话题,因为归根结底绝大部分任务是要来操作数据的。
其实在 Java 6 的时候,Oracle JDK 就提供了压缩字符串的特性,但是这个特性的实现并不是开源的,而且在实践中也暴露出了一些问题,所以在最新的 JDK 版本中已经将它移除了。
在 Java 9 中,我们引入了 Compact Strings 的设计,对字符串进行了大刀阔斧的改进。将数据存储方式从 char 数组,改变为一个 byte 数组加上一个标识编码的所谓 coder,并且将相关字符串操作类都进行了修改。另外,所有相关的 Intrinsic 之类也都进行了重写,以保证没有任何性能损失。
虽然底层实现发生了这么大的改变,但是 Java 字符串的行为并没有任何大的变化,所以这个特性对于绝大部分应用来说是透明的,绝大部分情况不需要修改已有代码。
当然,在极端情况下,字符串也出现了一些能力退化,比如最大字符串的大小。你可以思考下,原来 char 数组的实现,字符串的最大长度就是数组本身的长度限制,但是替换成 byte 数组,同样数组长度下,存储能力是退化了一倍的!还好这是存在于理论中的极限,还没有发现现实应用受此影响。
在通用的性能测试和产品实验中,我们能非常明显地看到紧凑字符串带来的优势,即更小的内存占用、更快的操作速度。
今天我从 String、StringBuffer 和 StringBuilder 的主要设计和实现特点开始,分析了字符串缓存的 intern 机制、非代码侵入性的虚拟机层面排重、Java 9 中紧凑字符的改进,并且初步接触了 JVM 的底层优化机制 intrinsic。从实践的角度,不管是 Compact Strings 还是底层 intrinsic 优化,都说明了使用 Java 基础类库的优势,它们往往能够得到最大程度、最高质量的优化,而且只要升级 JDK 版本,就能零成本地享受这些益处。
一课一练
关于今天我们讨论的题目你做到心中有数了吗?限于篇幅有限,还有很多字符相关的问题没有来得及讨论,比如编码相关的问题。可以思考一下,很多字符串操作,比如 getBytes()/String(byte[] bytes) 等都是隐含着使用平台默认编码,这是一种好的实践吗?是否有利于避免乱码?
请你在留言区写写你对这个问题的思考,或者分享一下你在操作字符串时掉过的坑,我会选出经过认真思考的留言,送给你一份学习鼓励金,欢迎你与我一起讨论。
你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。
分享给需要的人,Ta购买本课程,你将得18元
生成海报并分享
赞 52
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
第4讲 | 强引用、软引用、弱引用、幻象引用有什么区别?
下一篇
第6讲 | 动态代理是基于什么原理?
精选留言(142)
- Bin置顶2018-05-16jdk1.8中,string是标准的不可变类,但其hash值没有用final修饰,其hash值计算是在第一次调用hashcode方法时计算,但方法没有加锁,变量也没用volatile关键字修饰就无法保证其可见性。当有多个线程调用的时候,hash值可能会被计算多次,虽然结果是一样的,但jdk的作者为什么不将其优化一下呢?
作者回复: 这些“优化”在通用场景可能变成持续的成本,volatile read是有明显开销的; 如果冲突并不多见,read才是更普遍的,简单的cache是更高效的
共 10 条评论82 - 公号-技术夜未眠2018-05-15今日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封装等。展开
作者回复: 很到位
共 18 条评论625 - Hidden2018-05-16公司没有技术氛围,项目也只是 功能实现就好,不涉及优化,技术也只是传统技术,想离职,但又怕裸辞后的各种压力共 13 条评论219
- sea季陪我去看海2018-05-16作者我有个疑问,String myStr = "aa" + "bb" + "cc" + "dd"; 应该编译的时候就确定了,不会用到StringBuilder。理由是: String myStr = "aa" + "bb" + "cc" + "dd"; String h =aabbccdd Mystr ==h 上机实测返回的是true,如果按照你的说法,应该是返回false才对,因为你说拼接用到stringbuilder,那mystr应该是堆地址,h是常亮池地址。展开共 11 条评论83
- Jerry银银2018-05-15要完全消化一篇文章的所有内容,真得不是一天就能搞定的,可能需要一个月,甚至好几个月。就比如今天的字符串,我觉得这个话题覆盖的面太广:算法角度;易用角度;性能角度;编码传输角度等 但是好在,我获得见识。接下来,花时间慢慢研究呗,连大师们都花了那么多时间研究,我们多花点时间,很正常嘛😄 一点学习心得,和大家分享展开共 4 条评论57
- 愉悦在花香的日子里2018-05-15getBytes和String相关的转换时根据业务需要建议指定编码方式,如果不指定则看看JVM参数里有没有指定file.encoding参数,如果JVM没有指定,那使用的默认编码就是运行的操作系统环境的编码了,那这个编码就变得不确定了。常见的编码iso8859-1是单字节编码,UTF-8是变长的编码。
作者回复: 不错,莫依赖于不确定因素
共 2 条评论52 - Kongk0ng2018-05-16编译器为什么不把 String myStr = "aa" + "bb" + "cc" + "dd"; 默认优化成 String myStr = "aabbccdd"; 这样不是更聪明嘛共 3 条评论45
- Van2018-09-19String myStr = "aa" +"bb" + "cc" +"dd";反编译后并不会用到StringBuilder,老师反编译结果中出现StringBuilder是因为输出中拼接了字符串System.out.println("My String:" + myStr);
作者回复: 嗯,文中的例子有歧义,确实欠考虑
39 - 蔡光明2018-06-25回答一下上面一个人的问题,问题是“”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的, 个人理解,如有不对,请指正,谢谢😜展开共 3 条评论36
- DoctorDeng2018-09-21String 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,展开
作者回复: 思路比结论更有价值
共 8 条评论31 - Jerry银银2018-05-15特别喜欢这句话:“仅仅是字符串一个实现,就需要 Java 平台工程师和科学家付出如此大且默默无闻的努力,我们得到的很多便利都是来源于此。” 我想说,同学们,写代码的时候记得感恩哦😄 对于字符串的研究,我觉得能很好的理解计算机的本质和训练计算机思维,提升自己解决问题的能力。 小小的字符串有着诸多巨人的影子展开
作者回复: 非常感谢
27 - 王万云2018-05-20看大神的文章真的提高太多了,而且还要看评论,评论区也都是高手云集26
- X2019-04-23看了评论中的,感觉误区还是不少的,这里经过心爱之人极大的帮助,总结如下, 首先要搞清楚一个概念,就是到底创建了几个对象。基于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,进行对比展开共 2 条评论23
- a man is a man2018-10-101.通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。 2.JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与1.7之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。jdk1.7之后,常量池不仅仅可以存储对象,还可以存储对象的引用 3.在jdk1.7之前,字符串常量存储在方法区的PermGen Space(永久代)。在jdk1.7之后,字符串常量重新被移到了堆中。展开17
- 肖一林2018-05-15这篇文章写的不错,由浅入深,把来龙去脉写清楚了
作者回复: 谢谢认可
14 - 毛荣荣2018-10-25首先要明白,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展开共 2 条评论13
- фщэьшух2018-08-25回答上面的问题,问题如下: 作者我有个疑问,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展开12
- jamie2018-06-14编译器为什么不把 String myStr = "aa" + "bb" + "cc" + "dd"; 默认优化成 String myStr = "aabbccdd"; 这样不是更聪明嘛 这个我反编译试过,已经优化成aabbccdd了展开12
- 好运来2018-05-15老师,可以讲解这一句话的具体含义吗,谢谢! 你可以思考下,原来 char 数组的实现,字符串的最大长度就是数组本身的长度限制,但是替换成 byte 数组,同样数组长度下,存储能力是退化了一倍的!还好这是存在于理论中的极限,还没有发现现实应用受此影响。
作者回复: 已回复,一个char两个byte,注意下各个类型宽度
9 - 轩尼诗。2018-05-31String s = new String(“abc”) 创建了几个对象? 答案1:在字符串常量池中查找有没有“abc”,有则作为参数,就是创建了一个对象;没有则在常量池中创建,然后返回引用,那就是创建了两个对象。 答案2:直接在堆中创建一个新的对象。不检查字符串常量池,也不会把对象放入池中。 网上正确答案貌似是两个,求指教到底是哪个! 第一种可以写成String a = "abc";String s = new String(a) ,那么第一种解释说得通,String a = "abc"会在常量池创建“abc”。但是string类的intern()是在字符串常量池中查找该字符串,有就返回,没有就创建,如果第一种解释说得通,那这个方法就废了。展开共 1 条评论8