引言

众所周知在java里面除了8种基本数据类型的话,还有一种特殊的类型String,这个类型是我们每天搬砖都基本上要使用它。

String 类型可能是 Java 中应用最频繁的引用类型,但它的性能问题却常常被忽略。高效的使用字符串,可以提升系统的整体性能。当然,要做到高效使用字符串,需要深入了解其特性。

String类

我们可以看下String类的源码:

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

从源码上我们是不是可以发现String类是被final关键字所修饰的,String类的数据是通过char[] 数组来存储的。数组也是被final修饰的所以String 对象是不可被更改的。接下来我们再看看String的一些方法:像concat、replace、substring等都是返回了一个新的new String感兴趣的可以去看看String的一些常见方法。当我们执行这些方法之后最原始的字符串是没有改变的,都是返回新的字符串。

 public static void main(String[] args) {
String str = new String("java金融");
String str1 = str.substring(0, 4);
String str2 = str.concat("公众号");
String str3 = str.replace("java金融", "关注:【java金融】");
// 还有其他的方法
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
System.out.println(str);
}

输出结果

java
java金融公众号
关注:【java金融】
java金融

所以我们只要记住一点:“String对象一旦被创建就是固定不变的了,

String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。

字符串常量池

JVM中,为了减少字符串对象的重复创建,维护了一块特殊的内存空间,这块内存就被称为全局字符串常量池(string pool也有叫做string literal pool)。

字符串常量池的位置

字符串常量池所在的位置也是跟不同的jdk版本有关系的。

  • JDK6及之前字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代。
  • JDK7字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代。
  • JDK8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆里只不过把方法区的实现从永久代变成了元空间(Metaspace) 。

String# intern

String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用;

否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

上述定义出自《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》我们知道了这个 String::intern()这个方法的作用下面来看几道并没有什么用的题目看看你是否都能够回答对?

        String str2 = new String("java") + new String("金融"); // 1
str2.intern(); // 2
String str1 = "java金融"; // 3
System.out.println(str2 == str1);

这个代码在JDK6中输出结果是false,在jdk7输出是true

为何会因为不同的jdk版本输出结果不一样,因为不同版本字符串常量池的位置发生了变化。

下面来分析下为何会产生这种差异。

字符串虽然不属于基本数据类型但是它也可以想基本类型一样,直接通过字面量来赋值,同时也是可以通过new 来生成字符串对象。通过字面量赋值的方式和new 的方式 生成字符串还是有区别的。

  • 字面量赋值:通过字面量赋值(使用双引号声明出来的String)会先去常量池中查找是否已经有相同的字符串,如果已经存在栈中的引用直接指向该字符串,如果不存在就在常量中生成一个字符串再将栈中的引用指向该字符串。
  • new 的方式创建:而通过new的方式创建字符串时,就直接在堆中生成一个字符串的对象栈中的引用指向该对象。对于堆中的字符串对象,可以通过 intern() 方法来将字符串添加的常量池中,并返回指向该常量的引用。

    jdk6 结果是false,是因为常量池是在永久代的Perm区和java堆是两个区域。所以两个区域的对象地址比较是不同的。

    JDK7结果是true, 这个原因主要是从JDK 7及以后,HotSpot 将常量池从永久代移到了堆,正因为如此,JDK7 及以后的intern方法在实现上发生了比较大的改变,JDK7及以后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于如果在常量池找不到对应的字符串则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。所以为什么返回true 是因为执行完标号为1的时候常量池中没有"java金融"对象的,接下来标号为2的时候 会在常量池生成一个“java金融”的对象会直接存一个对堆中“java金融”的引用,标号为3:进行字面量赋值的时候常量池已经存在了所以直接返回该引用。所以都是指向堆中的字符串返回true

    如果把3行代码放到第一行上面结果又不一样了,感兴趣的可以动手试一试并且分析下原因哦。

string 常见性能优化

使用+号拼接字符串

字符串拼接是我们平时在代码中使用最频繁的了。

  • +号拼接静态字符串
   String str = "关注"+"公众号:"+"java金融";

我们可以通过反编译查看下上述代码:

 public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: ldc #2 // String 关注公众号:java金融
2: astore_1
3: return
LineNumberTable:
line 11: 0
line 12: 3
}

我们可以发现编译器直接帮我们优化了,直接生成了一个字符串“关注公众号:java金融” 并没有生成中间变量的String实例。如果我们上述代码稍微变化下

   public static void main(String[] args) {
String str ="关注";
String str1 = str + "公众号:java金融";
} stack=2, locals=3, args_size=1
0: ldc #2 // String 关注
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String 公众号:java金融
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_2
23: return
LineNumberTable:
line 11: 0
line 12: 3
line 13: 23

从反编译代码中我们会发现生成了StringBuilder对象来进行追加。

  • 所以String + 拼接变量的时候底层是通过StringBuilder来实现的,我们循环操作拼接字符串的时候也应当使用StringBuilder替代+,否则的话每一次循环都会创建

    一个StringBuilder 对象。
  • 对于静态字符串的拼接操作,Java在编译时会进行彻底的优化,会把多个拼接字符串在编译时合成一个单独的长字符串。

常见字符串经典面试题

关于字符串最常见的面试题,面试宝典常见的题目。

String s = new String("xyz") 创建了多少个实例?

一般的回答都会是2个,(一个是“xyz”,一个是指向“xyz”的引用对象s)

答案并没有那么简单哦,可以看看大佬的回答还是非常精彩的。

连接地址https://www.iteye.com/blog/rednaxelafx-774673(文末第一个参考地址)

结束

java面试必备String详解的更多相关文章

  1. 面试必备:详解Java I/O流,掌握这些就可以说精通了?

    @TOC Java IO概述 IO就是输入/输出.Java IO类库基于抽象基础类InputStream和OutputStream构建了一套I/O体系,主要解决从数据源读入数据和将数据写入到目的地问题 ...

  2. Java面试宝典答案详解与感悟(第一天)

    一.Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语法,集合的语法,io 的语法,虚拟机方面的语法. 1.一个".java&quo ...

  3. Java面试宝典答案详解与感悟(第二天)

    19.构造器 Constructor 是否可被 override? 答案:构造器Constructor不能被继承,因此不能重写Override,但是可以被重载Overload. 解析:构造器:在面向对 ...

  4. Java的String详解

    Java的String详解 博客分类: Java javaStringString详解常用方法  Java的String类在开发时经常都会被使用到,由此可见String的重要性.经过这次认真仔细的学习 ...

  5. java反射机制深入详解

    java反射机制深入详解  转自:http://www.cnblogs.com/hxsyl/archive/2013/03/23/2977593.html 一.概念 反射就是把Java的各种成分映射成 ...

  6. Java 8 Stream API详解--转

    原文地址:http://blog.csdn.net/chszs/article/details/47038607 Java 8 Stream API详解 一.Stream API介绍 Java8引入了 ...

  7. 国际化,java.util.ResourceBundle使用详解

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

  8. java之StringBuffer类详解

    StringBuffer 线程安全的可变字符序列. StringBuffer源码分析(JDK1.6): public final class StringBuffer extends Abstract ...

  9. java.util.ResourceBundle使用详解

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

随机推荐

  1. mysql The used table type doesn’t support FULLTEXT indexes 解决方案 (phpstudy 会出现),coten不会

    mysql The used table type doesn't support FULLTEXT indexes 是不支持全文索引,解决方案: 1.停掉mysql服务2.打开my.ini,搜索de ...

  2. 用python+sklearn(机器学习)实现天气预报数据 数据

    用python+sklearn机器学习实现天气预报 数据 项目地址 系列教程 勘误表 0.前言 1.爬虫 a.确认要被爬取的网页网址 b.爬虫部分 c.网页内容匹配取出部分 d.写入csv文件格式化 ...

  3. 关于es6 let var const 以及Symbol的总结

    ```javascript //es6新增块级作用域.声明变量用关键字let const , // es5中只有函数作用域和全局作用域,声明变量用关键字var  // let 和const 声明的变量 ...

  4. idea thymeleaf页面变量报错解决

    IDEA在thymeleaf页面编写变量,如${user.id}会出现红色波浪下划线错误,提示Validates unresolved references and invalid expressio ...

  5. Ossec 安装并配置邮件通知

    Ossec 安装并配置邮件通知 目录 Ossec 安装并配置邮件通知 1. 介绍 2. 软硬件环境 3. 安装步骤 3.1 Server 3.2 Agent 3.3 配置邮件通知 4. 参考资料 1. ...

  6. 【MySQL】SELECT语句 - 查询数据

    第4章 检索数据 文章目录 第4章 检索数据 1.SELECT语句 2.检索单个列 3.检索多个列 4.检索所有列 5.检索不同的行 6.限制结果 7.使用完全限定的表名 8.小结 简单记录 - My ...

  7. 【Linux】find删除365天以前的文件详细解析

    find . -name "*" -mtime +365 -exec rm -rf {} \; -mtime +365  文件被修改的时间,最后一次发生到现在365天 -atime ...

  8. 基于kubernetes实现coredns的及验证

    CoreDNS:  k8s内部的DNS ,用于对pod对service做记录的,好让其他的pod做访问 这里不用做过多的阐述 官方kube-dns现在已经没有在维护了,从Kubernetes 1.11 ...

  9. Dubbo中的统一契约是如何实现的?

    写在前面 之前,很多小伙伴私信我:如何才能快速的掌握Dubbo的核心原理和源码.所以,我写了一篇<我是如何在短期内快速掌握Dubbo的原理和源码的(纯干货)?>.对于Dubbo的源码解析系 ...

  10. CentOS 7 下安装 mysql ,以及用到的命令

    VMware虚拟机装好后,再装个CentOS7系统,以上环境自行百度... 一.Linux下查看mysql是否安装 1.指令ps -ef|grep mysql [root@localhost 桌面]# ...