(Java)关于String的面试问题

来源:互联网 发布:手机广联达预算软件 编辑:程序博客网 时间:2024/05/17 08:07

身边有些做Java开发的朋友,找工作时常常被考到一道关于字符串的题目。题目倒是很基础,然而根据朋友们事后的描述,有理由认为有的面试官自己都没有完全搞清楚这个问题。此外,在CSDN论坛中我也多次看到一些朋友在这个问题上的迷惑。索性把自己的理解写下来吧。

 

题目是一道简单的小程序,像下面这样:

public class Test1 { public static void main(String args[]) {  String s = new String("Hello");  System.out.println(s);  foo(s);  System.out.println(s); } public static void foo(String s) {  s = new String("World"); }}

问程序先后两次分别会输出什么。
第一个肯定输出“Hello”。关键是第二个,个别基础不牢的朋友可能被考倒,但基础稍微扎实一点的就不会。第二个输出的也是“Hello”。
到这里,万事大吉。面试官两眼放出喜悦的光芒,赞叹道:“嗯,不错不错。不会变的,对吧。因为String是immutable类型,immutable类型改不了的。”他说的“immutable”是指String类没有任何一个方法会改变对象的状态。

 

这可就有问题了。确实程序两次输出的都是“Hello”,但原因却不是String的immutable特性。不信换一个“mutable”的试试,比如StringBuilder或StringBuffer。

public class Test2 { public static void main(String args[]) {  StringBuilder s = new StringBuilder("Hello");  System.out.println(s);  foo(s);  System.out.println(s); } public static void foo(StringBuilder s) {  s = new StringBuilder("World"); }}

这次呢?会先输出“Hello”,然后输出“World”吗?不会,仍然是两次“Hello”。
足以说明这个问题跟String的immutable特性没有一毛钱的关系。不管是immutable类型,还是一般的类型,用这种方式写出来的程序main方法中前后两个对象肯定是一样的。

 

真正的原因是:Java语言的参数传递机制是“按值传递”(pass by value)。虽然Java中除基本数值类型外,其它变量都是引用,但那是另一回事。变量的语义(“引用”还是“值”)跟函数传参的机制是两个正交的概念。
各种编程语言中,最常见的参数传递方式不外乎按值传递和按引用传递(pass by reference)两种。比如,C和Java中函数参数都是按值传递的,C++和C#则同时支持按值传递和按引用传递。C和Java的不同在于,C是值类型的按值传递,而Java是引用类型的按值传递(基本的数值类型除外)。
按值传参最大的特点就是函数内部对“形参变量”本身的所做的修改外面的“实参变量”感知不到。

 

不过,对于StringBuilder来说我们至少有办法让它改变,比如像下面这样:

public class Test3 { public static void main(String args[]) {  StringBuilder s = new StringBuilder("Hello");  System.out.println(s);  foo(s);  System.out.println(s); } public static void foo(StringBuilder s) {  s.replace(0, s.length(), "World"); }}

体会到不同了吗?虽然参数变量本身是按值传递的,但这次我们对变量本身不感兴趣,我们不改变变量本身,而是通过它直接修改它所引用的那个对象。这一次,Java语言“几乎一切皆引用”的特点起作用了,程序第一次输出“Hello”,第二次输出“World”。熟悉C的朋友可能马上联想到:这很像C语言中通过指针来修改它指向的内容。
而先前那个String版本的程序,我们无法写出一个相应的可变版本。为什么呢?……“嗯,不错不错。因为String是immutable类型……”,这次真对了。——可见先前说“没有一毛钱的关系”也不尽然。因为immutable,导致我们无法针对String写出一个像Test3那样的“可变”程序,如此说来,“二分钱的关系”应该是有的。

 

“pass reference by value”就像C++ 11中的“An lvalue with rvalue reference type”一样,乍一看挺绕的,但只要仔细想清楚,让正交的东西“尘归尘 土归土”,就可以加深对语言的理解。

顺便问一句,您知道java.lang.StringBuilder和java.lang.StringBuffer的区别吗?好多面试官都喜欢“顺便”问问这个,而且答对了不会加分,答不上来却会扣分,至少扣印象分。:(

   Java.lang.StringBuffer线程安全的可变字符序列。类似于 String 的字符串缓冲区,但不能修改。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。 
   每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。从 JDK 5.0 开始,为该类增添了一个单个线程使用的等价类,即 StringBuilder 。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。 
    但是如果将 StringBuilder 的实例用于多个线程是不安全的。需要这样的同步,则建议使用 StringBuffer 。 

    那么下面我们再做一个一般性推导: 

    在大部分情况下 StringBuilder > StringBuffer 

    因此,根据这个不等式的传递定理: 在大部分情况下 
     StringBuilder > StringBuffer > String

====================================================================

String是一个类,但却是不可变的,所以String创建的算是一个字符串常量,StringBuffer和StringBuilder都是可变的。所以每次修改String对象的值都是新建一个对象再指向这个对象。而使用StringBuffer则是对StringBuffer对象本身进行操作。所以在字符串j经常改变的情况下,使用StringBuffer要快得多。

但在某些情况下:

Java代码
  1. String S1 = “Who” + “ is” + “ faster?”;   
  2. StringBuffer Stb = new StringBuilder(“Who”).append(“ is”).append(“ faster?”);  
Java代码
  1. String S1 = “Who” + “ is” + “ faster?”;   
  2. StringBuffer Stb = new StringBuilder(“Who”).append(“ is”).append(“ faster?”);  

S1的素对会比Stb快得多, 是因为JVM把String对象的拼接解释成了StringBuffer对象的拼接,其实在JVM就是:

Java代码
  1. String S1="Who   is faster?";  
Java代码
  1. String S1="Who   is faster?";  

不过如果,字符串是来自其他对象,如:

Java代码
  1. String s1="Who";   
  2. String s2=" is";   
  3. String s3=" faster?";   
  4. String   st=s1+s2+s3;  
Java代码
  1. String s1="Who";   
  2. String s2=" is";   
  3. String s3=" faster?";   
  4. String   st=s1+s2+s3;  

这个时候,String的速度就比不上StringBuffer了。

StringBuffer和StringBuilder

在操作字符串对象,StringBuiler是最快的,StringBuffer次之,String最慢。

Java代码
  1. public final class StringBuffer   
  2.    extends AbstractStringBuilder   
  3.    implements java.io.Serializable, CharSequence   
  4.   
  5. ublic final class StringBuilder   
  6.    extends AbstractStringBuilder   
  7.    implements java.io.Serializable, CharSequence  
Java代码
  1. public final class StringBuffer   
  2.    extends AbstractStringBuilder   
  3.    implements java.io.Serializable, CharSequence   
  4.   
  5. ublic final class StringBuilder   
  6.    extends AbstractStringBuilder   
  7.    implements java.io.Serializable, CharSequence  

可以看到StringBuffer和StringBuilder都继承继承了同一个抽象类。

Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。

每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。


StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。

java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步, StringBuilder的速度比StringBuffer快。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。两者的方法基本相同。

如果要多次操作字符串,使用StringBuffer和StringBuilder会提高效率,但至少在数量级超过百万时,StringBuilder的速度才会体现出来。



原创粉丝点击