推荐学习

  • 公司来了位阿里P8大神,看完他的手写“Kafka笔记”,万分膜拜
  • 牛掰!“基础-中级-高级”Java程序员面试集结,看完献出我的膝盖

真没想到,一个小小的String居然还有这么多窍门?

1. 看看源码

大家都知道, String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)。我们先来看看 String 的源码。

在 Java 8 中,String 内部使用 char 数组存储数据。

public final class String    implements java.io.Serializable, Comparable, CharSequence {    /** The value is used for character storage. */    private final char value[];}

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。

public final class String    implements java.io.Serializable, Comparable, CharSequence {    /** The value is used for character storage. */    private final byte[] value;    /** The identifier of the encoding used to encode the bytes in {@code value}. */    private final byte coder;}

value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

2. 不可变有什么好处呢

2.1 可以缓存 hash 值

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

2.2 String Pool 的使用

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

2.3 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。

2.4 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

3. 再来深入了解一下 String

3.1 “+” 连接符

字符串对象可以使用“+”连接其他对象,其中字符串连接是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的,对象转换为字符串是通过 toString 方法实现的。可以通过反编译验证一下:

/** * 测试代码 */public class Test {    public static void main(String[] args) {        int i = 10;        String s = "abc";        System.out.println(s + i);    }}/** * 反编译后 */public class Test {    public static void main(String args[]) {    //删除了默认构造函数和字节码        byte byte0 = 10;              String s = "abc";              System.out.println((new StringBuilder()).append(s).append(byte0).toString());    }}

由上可以看出,Java中使用"+"连接字符串对象时,会创建一个StringBuilder()对象,并调用append()方法将数据拼接,最后调用toString()方法返回拼接好的字符串。那这个 “+” 的效率怎么样呢?

3.2 “+”连接符的效率

使用“+”连接符时,JVM会隐式创建StringBuilder对象,这种方式在大部分情况下并不会造成效率的损失,不过在进行大量循环拼接字符串时则需要注意。比如:

String s = "abc";for (int i=0; i<10000; i++) {    s += "abc";}

这样由于大量StringBuilder创建在堆内存中,肯定会造成效率的损失,所以在这种情况下建议在循环体外创建一个StringBuilder对象调用append()方法手动拼接(如上面例子如果使用手动拼接运行时间将缩小到1/200左右)。

与此之外还有一种特殊情况,也就是当"+"两端均为编译期确定的字符串常量时,编译器会进行相应的优化,直接将两个字符串常量拼接好,例如

System.out.println("Hello" + "World"); /** * 反编译后 */ System.out.println("HelloWorld");

4. 字符串常量

4.1 为什么使用字符串常量?

JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性,常量池中一定不存在两个相同的字符串。

4.2 实现字符串常量池的基础

实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享。

运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收。

我们来看个小例子,了解下不同的方式创建的字符串在内存中的位置:

String string1 = "abc";   // 常量池String string2 = "abc";     // 常量池String string3 = new String("abc");  // 堆内存

5. String类常见的面试题

5.1 判断字符串s1和s2是否相等

public static void main(String[] args) {    String s1 = "123";    String s2 = "123";    String s3 = "1234";    String s4 = "12" + "34";    String s5 = s1 + "4";    String s6 = new String("1234");    System.out.println(s1 == s2);   // true    System.out.println(s1.equals(s2));  //true    System.out.println(s3 == s4);   //true    System.out.println(s3 == s5);   // false    System.out.println(s3.equals(s5)); //true    System.out.println(s3 == s6);   // false}

解析:

  • s1和s2:

String s1 = "123";先是在字符串常量池创建了一个字符串常量“123”,“123”常量是有地址值,地址值赋值给s1。接着声明 String s2=“123”,由于s1已经在方法区的常量池创建字符串常量"123",进入常量池规则:如果常量池中没有这个常量,就创建一个,如果有就不再创建了,故直接把常量"123"的地址值赋值给s2,所以s1==s2为true。

由于String类重写了equals方法,s1.equals(s2)比较的是字符串的内容,s1和s2的内容都是"123",故s1.equals(s2)为true。

  • s3和s4:

s3创建了一个新的字符串"1234",s4是两个新的字符串"12"和"34"通过"+“符号连接所得,根据Java中常量优化机制, “12” 和"34"两个字符串常量在编译期就连接创建了字符串"1234”,由于字符串"1234"在常量池中存在,故直接把"1234"在常量池的地址赋值给s4,所以s3==s4为true。

  • s3和s5:

s5是由一个变量s1连接一个新的字符串"4",首先会在常量池创建字符串"4",然后进行"+“操作,根据字符串的串联规则,s5会在堆内存中创建StringBuilder(或StringBuffer)对象,通过append方法拼接s1和字符串常量"4”,此时拼接成的字符串"1234"是StringBuilder(或StringBuffer)类型的对象,通过调用toString方法转成String对象"1234",所以s5此时实际指向的是堆内存中的"1234"对象,堆内存中对象的地址和常量池中对象的地址不一致,故s3==s5为false。

看下JDK8的API文档里的解释:

Java语言为字符串连接运算符(+)提供特殊支持,并为其他对象转换为字符串。字符串连接是通过StringBuilder (或StringBuffer )类及其append方法实现的。字符串转换是通过方法来实现toString,由下式定义0bject和继承由在Java中的所有类。有关字符串连接和转换的其他信息,请参阅Gosling,Joy 和Steele,Java 语言规范。

不管是常量池还是堆,只要是使用equals比较字符串,都是比较字符串的内容,所以s3.equals(s5)为true。

Java常量优化机制:给一个变量赋值,如果等于号的右边是常量,并且没有一个变量,那么就会在编译阶段计算该表达式的结果,然后判断该表达式的结果是否在左边类型所表示范围内,如果在,那么就赋值成功,如果不在,那么就赋值失败。但是注意如果一旦有变量参与表达式,那么就不会有编译期间的常量优化机制。

  • s3和s6:

String s6 = new String("1234");在堆内存创建一个字符串对象,s6指向这个堆内存的对象地址,而s3指向的是字符串常量池的"1234"对象的地址,故s3==s6为false。

5.2 创建多少个字符串对象?

String s0 = "123";String s1 = new String("123"); String s2 = new String("1" + "2");String s3 = new String("12") + "3";

解析:

  • String s0 = “123”;

字符串常量池对象:“123”,1个;

共1个。

  • String s1 = new String(“123”);

字符串常量池对象:“123”,1个;

堆对象:new String(“123”),1个;

共2个。

  • String s2 = new String(“1” + “2”);

字符串常量池对象:“12”,1个(Jvm在编译期做了优化,“1” + "2"合并成了 “12”);

堆对象:new String(“12”),1个

共2个。

由于s2涉及字符串合并,我们通过命令看下字节码信息:

javac StrTest.java  //编译源文件得到class文件javap -c StrTest.class  // 查看编译结果

得到字节码信息如下:

备注:以上编译结果基于Jdk1.8运行环境

我们可以很清晰看到,创建了一个新的String对象和一个字符串常量"12",new String("1" + "2") 相当于 new String("12"),共创建了2个字符串对象。

  • String s3 = new String(“12”) + “3”;

字符串常量池对象:“12”、“3”,2个,

堆对象: new Stringbuilder().append(“12”).append(“3”).toString();转成String对象,1个;

共3个。

我们同样看下编译后的结果:

可以看到,包括StringBuilder在内,共创建了4个对象,字符串"12"和字符串"3"是分开创建的,所以共创建了3个字符串对象。

总结:

new String()是在堆内存创建新的字符串对象,其构造参数中可传入字符串,此字符串一般会在常量池中先创建出来,new String()创建的字符串是参数字符串的副本,看下API中关于String构造器的解释:

String(String original)
初始化新创建的String对象,使其表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本。

所以new String()的方式创建字符串百分百会产生一个新的字符串对象,而类似于"123"这样的字符串对象则需要在创建之前看常量池中有没有,有的话就不创建,没有则创建新的对象。 "+"操作符连接字符串常量的时候会在编译期直接生成连接后的字符串,若该字符串在常量池已经存在,则不会创建新的字符串;连接变量的话则涉及StringBuilder等字符串构建器的创建,会在堆内存生成新的字符串对象。

以上就是我们给您带来的关于Java字符串的一些知识总结和面试技巧,你学废了吗?

作者:xqnode

原文链接:https://blog.csdn.net/xqnode/article/details/106663571

char转成string_真没想到,一个小小的String居然还有这么多窍门?相关推荐

  1. 真没想到,疫情让我实现了远程办公的夙愿

    昨天晚上,公号后台收到了一位读者小武的多条留言(内容大致如下),竟然让我心有戚戚焉. 二哥,真没想到,疫情让我实现了多年来的一个夙愿:远程办公.但说实话,心里挺不是滋味的. 放假前,我想着终于可以解放 ...

  2. 真没想到刚刚会把这个记录下来。嘿嘿。

    真没想到刚刚会把这个记录下来.嘿嘿. 等我们死去以后,会有人把这些收集整理起来,形成语录体著作吗? 哈哈!?. 脸太大了. http://www.uuzone.com/app/trackBack.do ...

  3. android 长时间充电,充电十分钟就能在畅玩N小时?没想到这样的手机居然真正存在!...

    充电十分钟就能在畅玩N小时?没想到这样的手机居然真正存在! 2020-05-02 10:35:49 0点赞 0收藏 0评论 玩游戏,刷抖音几乎成为了现代人每天闲暇时间必做的事情,尤其是<王者荣耀 ...

  4. 真没想到,Python还能实现5毛特效

    来源 | ZackSock(ID:ZackSock) 图源 | 视觉中国 Python牛已经不是一天两天的事了,但是我开始也没想到,Python能这么牛.前段时间接触了一个批量抠图的模型库,而后在一些 ...

  5. python合并视频和音频_真没想到,Python 还能实现 5 毛特效

    作者 | ZackSock 来源 | ZackSock(ID:ZackSock) Python牛已经不是一天两天的事了,但是我开始也没想到,Python能这么牛.前段时间接触了一个批量抠图的模型库,而 ...

  6. python可以做特效吗_真没想到,Python还能实现5毛特效

    来源 | ZackSock(ID:ZackSock) 图源 | 视觉中国 Python牛已经不是一天两天的事了,但是我开始也没想到,Python能这么牛.前段时间接触了一个批量抠图的模型库,而后在一些 ...

  7. 传一个实体一个string_没想到,一个小小的String还有这么多窍门

    1. 看看源码 大家都知道, String 被声明为 final,因此它不可被继承.(Integer 等包装类也不能被继承).我们先来看看 String 的源码. 在 Java 8 中,String ...

  8. 力扣每日一题:792. 匹配子序列的单词数【真没想到是一个二分】

    给定字符串 s 和字符串数组 words, 返回  words[i] 中是s的子序列的单词个数 . 字符串的 子序列 是从原始字符串中生成的新字符串,可以从中删去一些字符(可以是none),而不改变其 ...

  9. 又砸又烧,还要泼脏水,这谣言到底什么时候才没人信?发生这样的事真没想到.........

    全世界只有3.14 % 的人关注了 爆炸吧知识 曾经看到一条新闻,说的是漂亮国人民怀疑口罩上的那个鼻梁条是5G天线,它不仅能控制人,还能致癌. "所以这就是他们计划杀死我们的方式,他们把5G ...

最新文章

  1. Sql 2008 安装遇到的问题
  2. .NET Mvc Razor也可以这样玩!
  3. Buuctf(pwn) jarvisoj_tell_me_something 栈溢出
  4. 2019年下信息系统项目管理师真题讲解【视频+PDF下载】
  5. boost::proto模块实现构建算术表达式的简单示例 带有占位符的评估器的测试程序
  6. 获取Ip所在城市名与详细
  7. SQLite数据操作
  8. logstash grok切分nginx日志
  9. 观看台式计算机组成观后感,计算机组成原理实验一:运算器实验
  10. ngrok技术原理及下载使用
  11. querydsl动态 sql_QueryDSL-JPA
  12. 推荐给每个找工作的IT毕业生--打鸡血书
  13. iis10 asp 如何连接mdb_如何攻破一个网站
  14. 阿里P9专家:程序员未来职业发展路线
  15. UML动态模型图简单介绍
  16. 字节跳动面经(2020春招)
  17. 基于STM32MP157的鸿蒙学习(一)— 资料下载及入门
  18. Lanproxy 路径遍历漏洞 (CVE-2021-3019)
  19. Android - 九宫格
  20. 参会指南丨CDEC 2019中国数字智能生态大会正确参会姿势

热门文章

  1. [optee]-opteeTA启动的过程(open_ta的过程)
  2. python猜密码游戏规则_【python笔记 三 】python脚本实战---数字密码小游戏
  3. 点击EditText外部区域失去焦点的方法
  4. 静态反调试技术(2)
  5. androidstuido_schooltest_1
  6. 【开源】WeChatRobot+WeChatHelper 制作自己的微信机器人
  7. 10.Windows线程切换_FS段寄存器
  8. 寒假每日一题(入门组)【week7 完结】
  9. 查看电脑电池损耗的命令
  10. 第一章:递推与递归 【完结】