面试别再问我String了

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

字符串广泛应用 在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常被作为网络连接,文件操作等参数类型,倘若可改变,会出现意想不到的结果。

测试掌握程度

为了不浪费你的时间,请看下面的题目,若你一目了然,可以跳过本文了。


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)和方法区。简要图如下:


内存模型
  • str1 == str2

    
    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"被创建并驻留在了字符创常量池中(如果先前加载中没有创建驻留过)。
  1. 当执行此句时,因为"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)创建了一个对象。

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

往期文章一览

把「策略模式」应用到实际项目中

造个轮子,我学到了什么

技术面试中的软技能

不同时重写equals和hashCode又怎样!

关注微信公众号 「码上实战」 回复 :面试视频 和 架构师 送你非常不错的资料!

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,738评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,377评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,774评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,032评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,015评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,239评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,724评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,374评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,410评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,457评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,132评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,733评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,804评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,022评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,515评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,116评论 2 341