乐趣区

关于java:面试必考Java字符串

引言

家喻户晓在 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/re…(文末第一个参考地址)

完结

  • 因为本人满腹经纶,难免会有纰漏,如果你发现了谬误的中央,还望留言给我指出来, 我会对其加以修改。
  • 如果你感觉文章还不错,你的转发、分享、赞叹、点赞、留言就是对我最大的激励。
  • 感谢您的浏览, 非常欢送并感谢您的关注。


https://www.iteye.com/blog/re…
https://www.zhihu.com/questio…
https://www.cnblogs.com/paddi…
https://tech.meituan.com/2014…
https://www.jianshu.com/p/6be…
https://juejin.im/post/684490…

退出移动版