面试别再问我String了

阅读原文:面试别再问我String了java

字符串普遍应用 在Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来建立和操做字符串。面试

String 简介

String定义:正则表达式

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {}
复制代码

为何设计为不可变类呢?

String设计为不可变类主要考虑到:效率和安全。编程

  • 效率:1.在早期的JVM实现版本中,被final修饰的方法会被转为内嵌调用以提高执行效率。而从Java SE5/6开始,就渐渐摈弃这种方式了。所以在如今的Java SE版本中,不须要考虑用final去提高方法调用效率。只有在肯定不想让该方法被覆盖时,才将方法设置为final。2.缓存hashcode,String不可变,因此hashcode不变,这样缓存才有意义,没必要从新计算。
  • 安全:String常被做为网络链接,文件操做等参数类型,假若可改变,会出现意想不到的结果。

测试掌握程度

为了避免浪费你的时间,请看下面的题目,若你一目了然,能够跳过本文了。api

public class Test {
    public static void main(String[] args) {
        String str1 = "HelloFlyapi";
        String str2 = "HelloFlyapi";
        String str3 = new String("HelloFlyapi");
        String str4 = "Hello";
        String str5 = "Flyapi";
        String str6 = "Hello" + "Flyapi";
        String str7 = str4 + str5;

        System.out.println("str1 == str2 result: " + (str1 == str2));

        System.out.println("str1 == str3 result: " + (str1 == str3));

        System.out.println("str1 == str6 result: " + (str1 == str6));

        System.out.println("str1 == str7 result: " + (str1 == str7));

        System.out.println("str1 == str7.intern() result: " + (str1 == str7.intern()));

        System.out.println("str3 == str3.intern() result: " + (str3 == str3.intern()));
    }
}


复制代码

String 的建立方式

从上面的题中你会知道,String的建立方式有两种:数组

直接赋值

  • 此方式在方法区中字符串常量池中建立对象
    String str = "flyapi";
    复制代码

构造器

  • 此方式在堆内存建立对象缓存

    String str = new String();
    复制代码

分析

要理解String,那么要了解JVM内存中的栈(stack)、堆(heap)和方法区。简要图以下:安全

![JVM简内存分布图](images/JVM简内存.png)
![JVM简内存分布图](images/JVM简内存.png)

  • str1 == str2bash

    String str1 = "HelloFlyapi";
    String str2 = "HelloFlyapi";
    
    System.out.println(str1 == str2); // true
    复制代码

当执行第一句时,JVM会先去常量池中查找是否存在HelloFlyapi,当存在时直接返回常量池里的引用;当不存在时,会在字符创常量池中建立一个对象并返回引用。微信

当执行第二句时,一样的道理,因为第一句已经在常量池中建立了,因此直接返回上句建立的对象的引用。

  • str1 == str3

    String str1 = "HelloFlyapi";
    String str3 = new String("HelloFlyapi");
    
    System.out.println(str1 == str3); // false
    复制代码

执行第一句,同上第一句。

执行第二句时,会在堆(heap)中建立一个对象,当字符创常量池中没有‘HelloFlyapi’时,会在常量池中也建立一个对象;当常量池中已经存在了,就不会建立新的了。

  • str1 == str6

    String str1 = "HelloFlyapi";
    String str6 = "Hello" + "Flyapi";
    
    System.out.println(str1 == str6); // true
    复制代码

因为"Hello"和"Flyapi"都是常量,编译时,第二句会被自动编译为‘String str6 = "HelloFlyapi";’

  • str1 == str7

    String str1 = "HelloFlyapi";
    String str4 = "Hello";
    String str5 = "Flyapi";
    String str7 = str4 + str5;
    
    System.out.println(str1 == str7); // false
    复制代码

其中前三句变量存储的是常量池中的引用地址。

第四句执行时,JVM会在堆(heap)中建立一个以str4为基础的一个StringBuilder对象,而后调用StringBuilder的append()方法完成与str5的合并,以后会调用toString()方法在堆(heap)中建立一个String对象,并把这个String对象的引用赋给str7。

经常使用方法

下面是 String 类支持的方法,更多详细,参看 Java String API 文档:

方法 描述
char charAt(int index) 返回指定索引处的 char 值。
int compareTo(Object o) 把这个字符串和另外一个对象比较。
int compareTo(String anotherString) 按字典顺序比较两个字符串。
boolean endsWith(String suffix) 测试此字符串是否以指定的后缀结束。
boolean equals(Object anObject) 将此字符串与指定的对象比较。
boolean equalsIgnoreCase(String anotherString) 将此 String 与另外一个 String 比较,不考虑大小写。
byte[] getBytes() 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
byte[] getBytes(String charsetName) 使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
int indexOf(int ch) 返回指定字符在此字符串中第一次出现处的索引。
int indexOf(int ch, int fromIndex) 返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索。
int indexOf(String str) 返回指定子字符串在此字符串中第一次出现处的索引。
int indexOf(String str, int fromIndex) 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。
String intern() 返回字符串对象的规范化表示形式。
int lastIndexOf(int ch) 返回指定字符在此字符串中最后一次出现处的索引。
int lastIndexOf(int ch, int fromIndex) 返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。
int lastIndexOf(String str) 返回指定子字符串在此字符串中最右边出现处的索引。
int lastIndexOf(String str, int fromIndex) 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。
int length() 返回此字符串的长度。
boolean matches(String regex) 告知此字符串是否匹配给定的正则表达式。
String replace(char oldChar, char newChar) 返回一个新的字符串,它是经过用 newChar 替换此字符串中出现的全部 oldChar 获得的。
String replaceAll(String regex, String replacement) 使用给定的 replacement 替换此字符串全部匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement) 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit) 根据匹配给定的正则表达式来拆分此字符串。
boolean startsWith(String prefix) 测试此字符串是否以指定的前缀开始。
boolean startsWith(String prefix, int toffset) 测试此字符串从指定索引开始的子字符串是否以指定前缀开始。
String substring(int beginIndex) 返回一个新的字符串,它是此字符串的一个子字符串。
String substring(int beginIndex, int endIndex) 返回一个新字符串,它是此字符串的一个子字符串。
char[] toCharArray() 将此字符串转换为一个新的字符数组。
String toLowerCase() 使用默认语言环境的规则将此 String 中的全部字符都转换为小写。
String toUpperCase() 使用默认语言环境的规则将此 String 中的全部字符都转换为大写。
String trim() 返回字符串的副本,忽略前导空白和尾部空白。

String相关

因为String的不可变性致使,字符串变动时效率低下,在以后得JDK版本中出现了StringBuilder和StringBuffer.

可变性 线程安全
String 不可变 安全
StringBuffer 可变 安全
StringBuilder 可变 非安全
  • 使用选择
  1. 当有少许链接操做时,使用String
  2. 当单线程下有大量链接操做时,使用StringBuilder
  3. 当多线程下有大量链接操做时,使用StringBuffer

常见String面试题

  • String str = new String("abc")建立了多少个实例?

这个问题实际上是不严谨的,但面试通常会遇到,因此咱们要补充来讲明。

类的加载和执行要分开来说: 建立了两个

  1. 当加载类时,"abc"被建立并驻留在了字符创常量池中(若是先前加载中没有建立驻留过)。
  2. 当执行此句时,由于"abc"对应的String实例已经存在于字符串常量池中,因此JVM会将此实例复制到会在堆(heap)中并返回引用地址。

经过字节码咱们能够看到:

源码:String str = new String("abc")

字节码:

Code:
       0: new           #2 // class java/lang/String
       3: dup
       4: ldc           #3 // String abc
       6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)
       9: astore_1
      10: return
复制代码

执行时仅(#2)建立了一个对象。

关于这个面试题,能够看看一个超大牛的回答:rednaxelafx.iteye.com/blog/774673

给你们整理了一些面试文档和视频,公众号回复:架构师 或 面试视频

qrcode

本文优先发布于微信公众号:码上实战

qrcode
相关文章
相关标签/搜索