引言

众所周知在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. redis存json数据时选择string还是hash

    redis存json数据时选择string还是hash 我们在缓存json数据到redis时经常会面临是选择string类型还是选择hash类型去存储.接下来我从占用空间和IO两方面来分析这两种类型的 ...

  2. 使用 C# 9 的records作为强类型ID - 路由和查询参数

    上一篇文章,我介绍了使用 C# 9 的record类型作为强类型id,非常简洁 public record ProductId(int Value); 但是在强类型id真正可用之前,还有一些问题需要解 ...

  3. 深入剖析setState同步异步机制

    关于 setState setState 的更新是同步还是异步,一直是人们津津乐道的话题.不过,实际上如果我们需要用到更新后的状态值,并不需要强依赖其同步/异步更新机制.在类组件中,我们可以通过thi ...

  4. 【TNS】TNS-00515 TNS-12560 TNS-12545解决方案

    今天同事的plsql连接不上数据库,我用他的本地tnsping是不通的,于是上服务器上查看下,结果发现监听没起来,不知道怎么就断了 再次尝试重启 lsnrctl start 发现直接报错: NSLSN ...

  5. ctfshow—web—web7

    打开靶机 发现是SQL注入,盲注 过滤了空格符,可以用/**/绕过,抓包 直接上脚本 import requestss=requests.session()url='https://46a0f98e- ...

  6. Jmeter(三十六) - 从入门到精通进阶篇 - 设置负载阶梯式压测场景(详解教程)

    1.简介 在性能测试中,有时需要模拟一种实际生产中经常出现的情况,即:从某个值开始不断增加压力,直至达到某个值,然后持续运行一段时间,然后继续加压达到某个值持续运行,如此循环直到达到预期的峰值,运行一 ...

  7. 使用call、apply、bind继承及三者区别

    js里的继承方法有很多,比如:使用原型链的组合继承.es6的Class.寄生继承以及使用call.apply.bind继承.再说继承之前,我们先简单了解下它们的区别. 一.区别: 同:三者都是改变函数 ...

  8. Cookie&Session&Jsp总结

    知识点梳理 Cookie&Session&Jsp 1 会话技术 1.1 会话管理概述 1.1.1 会话技术介绍 会话:浏览器和服务器之间的多次请求和响应 (一次对话) 为了实现一些功能 ...

  9. allator 对springBoot进行加密

    1.对springboot项目添加jar包和xml文件 allatori.xml: <config> <input> <jar in="target/sprin ...

  10. 浅析Linux进程空间布局

    一.进程空间分布概述 对于一个进程,其空间分布如下图所示: 1.参数说明 程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码. 初始化过的数据(Data):在程序运行初已经对变量进行初 ...