写点什么

8000 字长文图解 String,这次彻底搞懂了

作者:Jackpop
  • 2022 年 4 月 09 日
  • 本文字数:6088 字

    阅读完需:约 20 分钟

Java 是最受欢迎的编程语言之一,它具有简单、安全、稳健等特性。

当你准备 Java 面试时,你会发现在大多数面试中,开始的问题都是关于 Java 字符串。

在这篇文章中,我将介绍前 20 个关于 Java 字符串的面试问题,读完这篇文章后,你将很轻松应对有关 Java String 相关的面试问题。

1. Java 中 String 是什么?它是一种数据类型吗?

字符串是 Java 中定义在 java.lang 包中的一个类。你可以将一连串的字符分配给一个字符串变量。例如,String name = "Gaurav"

另外,Java 中 String 不是一个像 int、char 或 long 那样的数据类型。

当你给 String 变量分配一个字符序列时,你就是在创建一个字符串对象。

每个字符串字面是一个字符串类的实例,它的值不能被改变。

2. C 语言中的字符串和 Java 中的字符串有什么区别?

如果你的简历中包含与 C 语言有关的内容,他们可以问你这个问题。

Java 和 C 语言中的字符串是完全不同的。

在 C 语言中,字符串是一个以 null 结尾的字符数组。

在下面的图片中,我展示了 C 语言和 Java 语言中的字符串的结构。



在 Java 中,字符串是比较抽象的。

字符串类来自 java.lang 包,有很多预定义的方法,程序员可以用这些方法对字符串进行操作或获得关于字符串的信息。

因此,在 Java 中,字符串比 C 语言的功能更丰富。

3. 什么是 Java 中的字符串池?

字符串池(String pool)是一种由 JVM 维护的特殊类型的内存。

字符串池用于存储唯一的字符串对象。

当你将相同的字符串字头分配给不同的字符串变量时,JVM 在字符串池中只保存一个字符串对象的副本,并且字符串变量将开始引用该字符串对象。

我在下图中对上述句子进行了图解。



维护这种特殊类型的内存的目的是内存优化。

4. 为什么字符串是不可变的?

在大多数的 Java 面试中,你会遇到这样的问题。

你认为 Java 语言设计者为什么要保持字符串的不可变性?

如果你把相同的字符串字面符号分配给许多字符串变量,JVM 将在 Java 字符串池中只保存一份字符串对象,这些变量将开始引用该字符串对象。

如果在这个问题之前没有人问过你关于字符串池的问题,请你介绍一下 Java 中字符串池概念的背景。请参考前面的问题。


另外,另一个原因是安全。我们知道,几乎每个 Java 程序都包含字符串,它被用来保存重要的数据,如用户名和密码。所以它不应该在中间被改变。否则,就会出现安全问题。

5. 以下代码将创建多少个对象?

String firstString = "Gaurav";String secondString = "Gaurav";String thirdString =  new String("Gaurav");
复制代码

看到上面的代码,只有两个字符串对象将被创建。前两个变量将引用同一个字符串对象,其值为 "Gaurav"。JVM 使用字符串池的概念,将重复的字符串对象只存储一份到字符串常量池。

但是,当我们使用new关键字来创建一个新的字符串时,一个新的字符串对象将被创建并存储在 Java 堆内存中。

所以对于第三个变量 thirdString,一个新的字符串对象将被创建并存储在 Java 堆空间中。

所以总共会有两个对象,一个来自 Java 字符串池,一个来自 Java 堆内存。

下面,我在下图中展示了这两个对象。



6. intern()方法的作用是什么?

intern()方法用来将字符串对象的唯一副本手动添加到 Java 字符串池中。

我们知道,当我们使用 new 关键字创建一个字符串时,它将被存储在堆内存中。

我们可以使用intern()方法将该字符串对象的唯一副本存储在 Java 字符串池中。

当你做这样的事情时,JVM 会检查字符串池中是否有相同值的字符串对象存在。

如果具有相同值的字符串对象存在,JVM 将简单地将该对象的引用提供给相应的字符串变量。

如果字符串池中没有相同值的字符串对象,JVM 会在字符串池中创建一个相同值的字符串对象,并将其引用返回到字符串变量中。

7. String 和 StringBuffer 之间的区别是什么?

字符串是 Java 中的一个 final 类,字符串是不可变的,这意味着我们不能再改变 String 对象的值。

由于字符串在应用程序中被广泛使用,我们必须对字符串对象进行多次操作。每次都会产生一个新的 String 对象,而之前的所有对象都会成为垃圾对象,给垃圾收集器带来压力。

因此,Java 团队引入了 StringBuffer 类。它是一个可变的 String 对象,这意味着你可以改变它的值。

字符串是不可变的,但 StringBuffer 是可变的。

8. StringBuffer 和 StringBuilder 的区别是什么?

我们知道字符串在 Java 中是不可变的。但是使用 StringBuffer 和 StringBuilder,你可以创建可编辑的字符串对象。

当 Java 团队意识到对可编辑字符串对象的需求时,他们引入了 StringBuffer 类。但是 StringBuffer 类的所有方法都是同步的。

这意味着在同一时间,只有一个线程可以访问 StringBuffer 的一个方法。

因此,它需要更多的时间。

后来,Java 团队意识到,让 StringBuffer 类的所有方法都同步化并不是一个好主意,于是他们引入了 StringBuilder 类。

StringBuilder 类的所有方法都不是同步的。


由于 StringBuffer 类的所有方法都是同步的,所以与 StringBuilder 相比,StringBuffer 是线程安全的,速度较慢,而且效率较低。

由于 StringBuilder 类的方法没有一个是同步的,所以与 StringBuffer 相比,StringBuilder 不是线程安全的,但是对比 StringBuffer 速度更快、效率更高。

9. 我们可以使用==运算符来比较字符串吗?有什么风险?

答案是肯定的,我们可以使用==运算符来比较字符串。

但是,当我们使用==运算符比较字符串时,无论这些字符串变量是否指向同一个字符串对象,我们都是在比较它们的对象引用。

大多数时候,开发人员想比较字符串的内容,但他们错误地用==运算符比较字符串,其实应该使用equals()方法,这导致了一个错误。

下面,我给出了一个程序,对比一下使用==运算符和equals()方法进行的字符串比较。

publicclass StringCompareUsingEqualsOperator {  public static void main(String[] args) {  String firstString = "Gaurav"; String secondString = "Gaurav";  String thirdString =  new String("Gaurav");  System.out.print("Case 1 : "); System.out.println(firstString == secondString); // true  System.out.print("Case 2 : "); System.out.println(firstString == thirdString); // false  // Comparing strings using equals() method System.out.print("Case 3 : "); System.out.println(firstString.equals(thirdString)); // true } }
复制代码

上述程序的输出如下:

Case 1 : trueCase 2 : falseCase 3 : true
复制代码

在'Case 1'中,我们使用等价运算符(==)比较 firstString 和 secondString,因为这两个变量都指向同一个字符串对象,所以会打印为真。

在'Case 2'中,我们使用等价运算符(==)比较 firstString 和 thirdString,因为两个变量都没有指向同一个字符串对象,所以会打印出 false。

你可以看到,对于 thirdString,我们使用了 new 关键字,它在 Java 堆内存中创建了一个新对象。

在 "Case 3 "中,我们使用 equals()方法比较了 firstString 和 thirdString。即使两者是不同的字符串对象,它的内容也是一样的,因此它打印的是 true。

10. 比较字符串的方法有哪些?

我们可以使用equals()方法、==运算符和compareTo()方法来比较字符串。

当我们使用 equals()方法比较字符串时,我们在比较字符串的内容,这些字符串是否有相同的内容。

当我们使用==操作符比较字符串时,我们是在比较字符串的引用,即这些变量是否指向同一个字符串对象。

此外,我们还可以按字母顺序比较字符串(按字母顺序比较字符串)。我们可以使用 compareTo()方法来比较按字母顺序排列的字符串。

compareTo()方法返回一个负整数、0 或一个正整数。

firstString.compareTo(secondString)
复制代码

如果 firstString 小于 secondString,它将返回一个负整数,即 firstString < secondString → 返回一个负整数。

如果 firstString 等于 secondString,它将返回 0,即 firstString == secondString → 返回 0。

如果 firstString 大于 secondString,将返回一个正整数,即 firstString > secondString → 返回一个正整数。

11. substring()方法的用途是什么?

Java 中的 substring()方法返回指定字符串的一个子字符串。

子字符串的创建取决于传递给 substring()方法的参数。

Substring 方法有两种使用用法:

  • substring(int beginIndex)

  • substring(int beginIndex, int endIndex)

在第一个方法中,我们只给出了参数 beinIndex,而在第二个变体中,我们同时给出了 beginIndex 和 endIndex。

对于第一个变体,子字符串将从 beginIndex(包括)取到字符串的最后一个字符。

对于第二个变体,子字符串将从 beginIndex(包括)取到 endIndex(不包括)。

请看下面的图来理解 substring()方法。



在第一张图中,使用 substring()的第一种用法。

String name = "Gaurav Kukade";String result = name.substring(4); System.out.println(result);
复制代码



在第二张图中,我有 substring()方法的第二种用法。

String name = "Gaurav Kukade";String result = name.substring(4, 9); System.out.println(result);
复制代码

12. 如何检查字符串是否为空?

Java 字符串类有一个特殊的方法来检查字符串是否为空。

isEmpty()方法检查字符串的长度是否为零,如果是零,意味着字符串是空的,isEmpty()方法将返回 true。

如果字符串的长度不是零,那么 isEmpty()方法将返回 false。

13. 什么是 Java 字符串中的 format()方法?format()方法和 printf()方法的区别是什么?

format()方法和 printf()方法都是对字符串进行格式化。

唯一的区别是 format()方法返回格式化的字符串,而 printf()方法则是打印格式化的字符串。

因此,当你想在程序中使用格式化的字符串时,你可以使用 format 方法。

而当你想直接打印格式化的字符串时,你可以使用 printf()方法。

14. String 在 Java 中是 "线程安全"的吗?

是的,字符串是线程安全的。

正如我们所知,在 Java 中,字符串是不可变的。这意味着一旦我们创建了一个字符串,我们就不能再修改它。

因此,不存在多个线程访问一个字符串对象的问题。

15. 为什么大多数情况下字符串被用作 HashMap 的键?

这个字符串是不可改变的。所以有一点是固定的,那就是它一旦创建就不会被改变。

因此,计算出来的哈希码可以被缓存并在程序中使用。

这将节省我们一次又一次计算哈希码的精力,所以,字符串可以比其他 HashMap 键对象使用起来更加有效。

16. 你能将 String 转换为 Int,反之亦然吗?

是的,你可以将字符串转换为 int,反之亦然。

你可以使用 Integer 类的 valueOf()方法和 parseInt()方法将字符串转换为整数。

同时,你也可以使用 String 类的 valueOf()方法将整数转换为字符串。

下面,我给出了一个程序,显示了字符串到整数和整数到字符串的转换。

publicclass Conversion{ public static void main(String [] args){  String str = "1254";  int number = 7895;  // convert string to int using Integer.parseInt() method int parseIntResult1 = Integer.parseInt(str);  // convert string to int using Integer.valueOf() method int valueOfResult1 = Integer.valueOf(str);  System.out.println("Converting String to Integer:"); System.out.println("Using the Integer.parseInt() method : "+parseIntResult1); System.out.println("Using the Integer.valueOf() method : "+valueOfResult1);  System.out.println("\n"); // convert integer to string using String.valueOf() method String valueOfResult2 = String.valueOf(number);  System.out.println("Converting Integer to String :"); System.out.println("Using the String.valueOf() method : "+valueOfResult2);  }}
复制代码

上述程序的输出如下:

Converting String to Integer:Using the Integer.parseInt() method : 1254Using the Integer.valueOf() method : 1254  Converting Integer to String :Using the String.valueOf() method : 7895
复制代码

17. 什么是 split()方法?

split 方法用于根据提供的 regex 表达式来分割字符串。

该方法将返回一个分割子字符串的数组:

publicclass SplitExample {  public static void main(String[] args) { String name = "My, name, is ,Gaurav!";  String [] substringArray = name.split(",");  for(String substring : substringArray) { System.out.print(substring); } }}
复制代码

上述程序的输入为:

My name is Gaurav!
复制代码

18. "Gaurav Kukade".equals(str)和 str.equals("Gaurav Kukade")之间有什么区别?

两者看起来都一样,它将检查字符串变量 str 的内容是否等于字符串 "Gaurav Kukade"。

但是当字符串变量 str = null 时,它们就不同了。

第一个代码片断将返回 false,但第二个代码片断将抛出 NullPointerExpection。

下面我给出了一个程序,它使用 equals()方法比较字符串的两种方式。

publicclass StringExample {  public static void main(String[] args) {  String str = "Gaurav Kukade";  System.out.println("Gaurav Kukade".equals(str)); // true  System.out.println(str.equals("Gaurav Kukade")); // true } }
复制代码

上述程序的输入为:

truetrue
复制代码

两次都是打印为 true,因为两个字符串的内容都是相等的。

现在,我们将检查一个程序,其中 str=null:

publicclass StringNullExample {  public static void main(String[] args) {  String str = null;  System.out.println("Gaurav Kukade".equals(str)); // false  System.out.println(str.equals("Gaurav Kukade")); // NullPointerException }}
复制代码

上述程序的输入为:

falseException in thread "main" java.lang.NullPointerException at StringNullExample.main(StringNullExample.java:14)
复制代码

我们可以看到上面的输出,对于第一个代码片断,它是打印 false,但对于第二个代码片断,它是抛出 NullPointerException。

这是避免 java 字符串中出现空指针异常的最重要的技巧之一。

19. 在 java 中,我们可以在 switch case 中使用一个字符串吗?

是的,从 Java 7 开始,我们可以在 switch 情况下使用 String。

下面,我给出了一个程序,展示了字符串在 switch 情况下的使用。

publicclass StringInSwitchExample  {     public static void main(String[] args)     {         String str = "two";         switch(str)         {             case"one":                 System.out.println("January");                 break;             case"two":                 System.out.println("February");                 break;             case"three":                 System.out.println("March");                 break;             default:                 System.out.println("Invalid month number");         }     } }
复制代码

上述程序的输入为:

February
复制代码

20. 在 Java 中如何使用+运算符进行字符串连接?

+运算符是唯一的重载运算符,你可以把它用于两个数字的相加,也可以用于字符串连接。

如果你使用的是 1.5 或以上的 Java 版本,字符串连接在内部使用 StringBuilder 的 append()方法。而对于低于 1.5 的版本,它使用 StringBuffer 类的 append()方法。


hello,大家好,我是 Jackpop,硕士毕业于哈尔滨工业大学,曾在华为、阿里等大厂工作,如果你对升学、就业、技术提升等有疑惑,不妨交个朋友:

https://mp.weixin.qq.com/s/fCHn8JpLQDH-M_QkVxwR1w

发布于: 刚刚阅读数: 2
用户头像

Jackpop

关注

还未添加个人签名 2020.09.16 加入

公众号:平凡而诗意,微信:code_7steps,全网粉丝超20万,技术进阶、优质资源、实用工具,欢迎关注!

评论

发布
暂无评论
8000字长文图解String,这次彻底搞懂了_Jackpop_InfoQ写作平台