引言

众所周知在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. JIRA对接钉钉群机器人-实现任务的指派通知

    一.前提 Jira Software.钉钉群.RESTful服务.LDAP服务 二.流程图 三.对接步骤 1.创建项目群,把相关人员拉入群 2.钉钉群的智能群助手里添加自定义机器人 3.设置机器人,安 ...

  2. Token验证的流程及如何准确的判断一个数据的类型

    Token验证的流程: 1,客户端使用用户名跟密码请求登录:2,服务端收到请求,去验证用户名与密码:3,验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端:4,客户端收到 T ...

  3. Soat控制HAProxy 动态增减服务器

    Soat控制HaProxy 动态增减服务器 安装HaProxy-1.5.18: yum install haproxy -y yum install socat -y HaProxy-1.5.18 配 ...

  4. 用percona monitoring plugins 监控mysql

    下载:http://www.percona.com/redir/downloads/percona-monitoring-plugins/1.1.1/percona-zabbix-templates- ...

  5. 【Linux】ssh设置了密钥,但ssh登陆的时候还需要输入密码

    ------------------------------------------------------------------------------------------------- | ...

  6. 【ORACLE】awr报告问题分析

    本文转自:http://www.linuxidc.com/Linux/2015-10/123959.htm 感谢分享 1.问题说明 运维人员都有"节日休假恐怖症",越到节日.休假和 ...

  7. Kafka 探险 - 生产者源码分析: 核心组件

    这个 Kafka 的专题,我会从系统整体架构,设计到代码落地.和大家一起杠源码,学技巧,涨知识.希望大家持续关注一起见证成长! 我相信:技术的道路,十年如一日!十年磨一剑! 往期文章 Kafka 探险 ...

  8. Request&Response总结

    Request&Response Request 请求对象的类视图 请求对象常用方法 获取请求路径 返回值 方法名 说明 String getContextPath() 获取虚拟目录名称 St ...

  9. 无法获取 vmci 驱动程序版本: 句柄无效。 驱动程序 vmci.sys 版本不正确。请尝试重新安装 VMware Workstation。 打开模块DevicePowerOn电源失败。

    1.别打开电源,然后到虚拟机安装文件夹内.2.找到你的虚拟机系统文件中后缀为vmx的文件,右击用记事本或者Notepad++打开.2.搜索找到vmci0.present='TRUE',字段,把true ...

  10. Bitter.Core系列 十二 :Bitter ORM NETCORE ORM 全网最粗暴简单易用高性能的 NETCore 之 支持的 where 条件表达式

    Bitter.Core 内置了支持大部分的 linq 的条件表达式.基本上符合我们 where 条件所需.Bitter.Core 的支持 表达式内置  扩展 有: EndsWith    在Sql 解 ...