关于后端:Java字符串String的那些事

引言

家喻户晓在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的一些常见办法。当咱们执行这些办法之后最原始的字符串是没有扭转的,都是返回新的字符串。
《2020最新Java根底精讲视频教程和学习路线!》

 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语言", "啥也不是");
        // 还有其余的办法
        System.out.println(str1);
        System.out.println(str2);
        System.out.println(str3);
        System.out.println(str);
    }

输入后果

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                 
         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://zhuanlan.zhihu.com/p/…

原文链接:https://juejin.cn/post/689963…

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理