当前位置:   article > 正文

Java中String详解(从原理理解经典面试题)_java string 面试

java string 面试

本篇文章我先通过经典面试题,筛选需要观看本篇文章的朋友,然后咱们介绍String的基本特性,通过基本特性就可以找到面试题的答案。最后咱们再深入每个面试题,通过字节码、编译原理、基本特性深入剖析所有的面试题,让大家最终能通过原理理解所有的面试题,而不是死记硬背。

1、经典面试题

如果你可以全部答对并且是从原理上答对的,那您是真的完全理解String了,如果全部答对但是都是死记硬背或者未全部答对,则通过本篇文章相信您完全可以不用死记硬背就能答对,所有题目。

  1. import org.junit.Test;
  2. /**
  3. * @author liuchao
  4. * @date 2023/3/2
  5. */
  6. public class StringTest {
  7. @Test
  8. public void test1() {
  9. String str1 = "abc";
  10. String str2 = "abc";
  11. System.out.println(str1 == str2);
  12. }
  13. @Test
  14. public void test2() {
  15. String str1 = "abc";
  16. String str2 = "abc";
  17. str2 = "efg";
  18. System.out.println(str1 == str2);
  19. }
  20. @Test
  21. public void test3() {
  22. String str1 = "abc";
  23. String str2 = "abc";
  24. str2 += "efg";
  25. System.out.println(str1 == str2);
  26. }
  27. @Test
  28. public void test4() {
  29. String str1 = "abc";
  30. String str2 = str1.replace('a', 'e');
  31. System.out.println(str1 == str2);
  32. }
  33. @Test
  34. public void test5() {
  35. String str1 = "a" + "b" + "c";
  36. String str2 = "abc";
  37. System.out.println(str1 == str2);
  38. }
  39. @Test
  40. public void test6() {
  41. String str1 = "Hello";
  42. String str2 = "String";
  43. String str3 = "HelloString";
  44. String str4 = "Hello" + "String";
  45. String str5 = str1 + "String";
  46. String str6 = "Hello" + str2;
  47. String str7 = str1 + str2;
  48. System.out.println(str3 == str4);
  49. System.out.println(str3 == str5);
  50. System.out.println(str3 == str6);
  51. System.out.println(str3 == str7);
  52. System.out.println(str5 == str6);
  53. System.out.println(str5 == str7);
  54. System.out.println(str6 == str7);
  55. String str8 = str6.intern();
  56. System.out.println(str3 == str8);
  57. }
  58. }
  59. /**
  60. * @author liuchao
  61. * @date 2023/3/2
  62. */
  63. public class StringExer {
  64. String str = "hello";
  65. char[] ch = {'a', 'b'};
  66. public void change(String str, char[] ch) {
  67. str = "string";
  68. ch[0] = 'e';
  69. }
  70. public static void main(String[] args) {
  71. StringExer exer = new StringExer();
  72. exer.change(exer.str, exer.ch);
  73. //请问 输出值 分别是什么
  74. System.out.println(exer.str);
  75. System.out.println(exer.ch);
  76. }
  77. }
  78. /**
  79. * @author liuchao
  80. * @date 2023/3/3
  81. */
  82. public class Test3 {
  83. @Test
  84. public void test1() {
  85. String str = new String("ab");
  86. //问 new String("ab") 会创建几个对象呢?
  87. }
  88. @Test
  89. public void test2(){
  90. String str = new String("a") + new String("b");
  91. //问 new String("a") + new String("b"); 会创建几个对象呢?
  92. }
  93. }

2、String基本特性

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {

①、String:字符串,使用一对""引起来

②、String类被声明为final的,不可继承

③、String实现了Serializable接口:表示字符串是支持序列化的;实现Comparable接口:表示String可以比较大小

④、String在jdk8及以前内部定义了final char[] value用于存储字符串数据。jdk9时改为byte[] 存储字符串数据。

这里说下为什么jdk9要改为byte[]数组

官网解释:https://openjdk.org/jeps/254

官网解释翻译:

  • 动机(为什么要更改)

目前String类的实现将字符存储在一个char数组中,每个字符使用两个字节(16位)。从许多不同的应用中收集到的数据表明,字符串是堆使用的主要组成部分,此外,大多数字符串对象只包含Latin-1(拼音之类的字符,一个拼音等于一个byte,用char存储就有点浪费了)字符。这些字符只需要一个字节的存储空间,因此这些字符串对象的内部字符数组中有一半的空间没有被使用。

  • 说明

我们建议将String类的内部表示方法从UTF-16字符数组改为字节数组加编码标志域。新的String类将根据字符串的内容,以ISO-8859-1/Latin-1(每个字符一个字节)或UTF-16(每个字符两个字节)的方式存储字符编码。编码标志将表明使用的是哪种编码。

⑤、String:代表不可变的字符序列。简称不可变性。

  • 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。

  • 当对现有字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

  • 当调用String的replace()方法修改指定字符或者字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

⑥、通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值什么在字符串常量池中。

⑦、字符串常量池是不会存储相同内容的字符串的。

String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009。如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降。

使用-XX:StringTableSize可设置StringTable的长度

  • 在jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTablesize设置没有要求

  • 在jdk7中,StringTable的长度默认值是60013,StringTablesize设置没有要求

  • 在jdk8中,设置StringTable长度的话,1009是可以设置的最小值

3、String内存分配

在Java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。

常量池就类似一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种。

  • 直接使用双引号声明出来的String对象会直接存储在常量池中。 eg: String test = "test";

  • 如果不是用双引号声明的String对象,可以使用String提供的intern()方法。动态创建字符串放入常量池

Java 6及以前,字符串常量池存放在永久代

Java 7中 Oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内

  • 所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。

  • 字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在Java 7中使用String.intern()。

Java8元空间,字符串常量在堆

4、字符串拼接操作

①、常量与常量的拼接结果在常量池,原理是编译期优化

②、常量池中不会存在相同内容的变量

③、只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder

④、如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址

5、通过字节码分析每种题目

5.1、常量池不允许相同字符串重复存在

代码:

  1. import org.junit.Test;
  2. /**
  3. * @author liuchao
  4. * @date 2023/3/2
  5. */
  6. public class StringTempTest {
  7. @Test
  8. public void test() {
  9. String str1 = "abc";
  10. String str2 = "abc";
  11. System.out.println(str1 == str2);
  12. }
  13. }

通过字节码指令可以看出,加载的是同一个常量在地址

注:使用插件为jclasslib,可以访问https://blog.csdn.net/u011837804/article/details/129064876 查看使用方式

5.2、常量与常量的拼接结果在常量池,原理是编译期优化佐证

代码:

  1. import org.junit.Test;
  2. /**
  3. * @author liuchao
  4. * @date 2023/3/2
  5. */
  6. public class StringTempTest {
  7. @Test
  8. public void test() {
  9. String str1 = "a" + "b" + "c";
  10. String str2 = "abc";
  11. System.out.println(str1 == str2);
  12. }
  13. }

javac编译后的.class文件反编译看下,证明常量与常量的拼接结果在常量池,原理是编译期优化。

5.3、只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder

代码:

  1. import org.junit.Test;
  2. /**
  3. * @author liuchao
  4. * @date 2023/3/2
  5. */
  6. public class StringTempTest {
  7. @Test
  8. public void test() {
  9. String str1 = "Hello";
  10. String str3 = "HelloString";
  11. String str5 = str1 + "String";
  12. System.out.println(str5 == str3);
  13. }
  14. }

代码和字节码对比图片

咱们对字节码逐行分析

  1. # 对应代码11行
  2. 0 ldc #2 <Hello>
  3. 2 astore_1
  4. # 对应代码12行
  5. 3 ldc #3 <HelloString>
  6. # 对应代码14行开始
  7. # 咱们从字节码不难看出,这里先是new StringBuilder() 然后又调用append()方法,
  8. # 最后调用了toString()方法
  9. 5 astore_2
  10. 6 new #4 <java/lang/StringBuilder>
  11. 9 dup
  12. 10 invokespecial #5 <java/lang/StringBuilder.<init> : ()V>
  13. 13 aload_1
  14. 14 invokevirtual #6 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
  15. 17 ldc #7 <String>
  16. 19 invokevirtual #6 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
  17. 22 invokevirtual #8 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
  18. 25 astore_3
  19. # 对应代码14行结束
  20. 26 getstatic #9 <java/lang/System.out : Ljava/io/PrintStream;>
  21. 29 aload_3
  22. 30 aload_2
  23. 31 if_acmpne 38 (+7)
  24. 34 iconst_1
  25. 35 goto 39 (+4)
  26. 38 iconst_0
  27. 39 invokevirtual #10 <java/io/PrintStream.println : (Z)V>
  28. 42 return

咱们在看看StringBuilder方法的toString()方法具体信息

咱们发现toString()方法实际就是new String()对象,咱们知道通过关键字 new创建的对象,对象实例都是存储在堆上的(非字符串常量池的一块区域),是不是就佐证了,上述代码结果为false

5.4、String intern()方法

通过翻译方法注释,佐证此方法结果是将对象值,在字符串常量池创建一份

a string that has the same contents as this string, but is
guaranteed to be from a pool of unique strings.

翻译:与此字符串具有相同内容,但保证来自唯一字符串池的字符串

5.5、会创建几个对象

通过字节码查看创建了几个对象

6、面试题答案公布

相信大家看完上述的理论+佐证,可以很简单的理解面试题,并且很快解答出来,咱们来看看所有题目的答案。

  1. import org.junit.Test;
  2. /**
  3. * @author liuchao
  4. * @date 2023/3/2
  5. */
  6. public class StringTest {
  7. @Test
  8. public void test1() {
  9. String str1 = "abc";
  10. String str2 = "abc";
  11. System.out.println(str1 == str2);//true
  12. }
  13. @Test
  14. public void test2() {
  15. String str1 = "abc";
  16. String str2 = "abc";
  17. str2 = "efg";
  18. System.out.println(str1 == str2);//不变性,所以为 false
  19. }
  20. @Test
  21. public void test3() {
  22. String str1 = "abc";
  23. String str2 = "abc";
  24. str2 += "efg";
  25. System.out.println(str1 == str2);//不变性,所以 false
  26. }
  27. @Test
  28. public void test4() {
  29. String str1 = "abc";
  30. String str2 = str1.replace('a', 'e');
  31. System.out.println(str1 == str2);//不变性,所以 false
  32. }
  33. @Test
  34. public void test5() {
  35. String str1 = "a" + "b" + "c";
  36. String str2 = "abc";
  37. System.out.println(str1 == str2);//编译优化 所以 true
  38. }
  39. @Test
  40. public void test6() {
  41. String str1 = "Hello";
  42. String str2 = "String";
  43. String str3 = "HelloString";
  44. String str4 = "Hello" + "String";
  45. String str5 = str1 + "String";
  46. String str6 = "Hello" + str2;
  47. String str7 = str1 + str2;
  48. System.out.println(str3 == str4);//编译优化 所以 true
  49. System.out.println(str3 == str5);//两个变量存储的位置不一致,所以 false
  50. System.out.println(str3 == str6);//两个变量存储的位置不一致,所以 false
  51. System.out.println(str3 == str7);//两个变量存储的位置不一致,所以 false
  52. System.out.println(str5 == str6);//都未存储在字符串常量池中,所以地址不一致 false
  53. System.out.println(str5 == str7);//都未存储在字符串常量池中,所以地址不一致 false
  54. System.out.println(str6 == str7);//都未存储在字符串常量池中,所以地址不一致 false
  55. String str8 = str6.intern();
  56. System.out.println(str3 == str8);//intern 将str6内容重新在常量池中创建一份,但是常量池中不允许重复值,所以true
  57. }
  58. }
  59. /**
  60. * @author liuchao
  61. * @date 2023/3/2
  62. */
  63. public class StringExer {
  64. String str = "hello";
  65. char[] ch = {'a', 'b'};
  66. public void change(String str, char[] ch) {
  67. str = "string";
  68. ch[0] = 'e';
  69. }
  70. public static void main(String[] args) {
  71. StringExer exer = new StringExer();
  72. exer.change(exer.str, exer.ch);
  73. System.out.println(exer.str);//hello
  74. System.out.println(exer.ch);//eb
  75. }
  76. }
  77. /**
  78. * @author liuchao
  79. * @date 2023/3/3
  80. */
  81. public class Test3 {
  82. @Test
  83. public void test1() {
  84. String str = new String("ab");
  85. //问 new String("ab") 会创建几个对象呢?
  86. /**
  87. * 答案:2个对象
  88. * 对象1:new String()
  89. * 对象2: 常量池中的"ab"
  90. */
  91. }
  92. @Test
  93. public void test2(){
  94. String str = new String("a") + new String("b");
  95. //问 new String("a") + new String("b"); 会创建几个对象呢?
  96. /**
  97. * 答案: 6个对象
  98. * 对象1:new StringBuilder()
  99. * 对象2:new String("a")
  100. * 对象3:常量池中的"a"
  101. * 对象4:new String("b)
  102. * 对象5:常量池中的"b"
  103. * 对象6:StringBuilder.toString()底层 又 new String() 了一个对象
  104.         * 这里注意:StringBuilder.toString() 虽然 new 了一个Stirng,
  105. * 但是 字符串常量池中是没有生成”ab“
  106. */
  107. }
  108. }

最后:希望对大家的面试有帮助,如果有写错的地方,欢迎大家留言指正,谢谢。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/237844
推荐阅读
相关标签
  

闽ICP备14008679号