Contents
  1. 1. String 的修改原理
  2. 2. String类的了解
  3. 3. 深入理解String StringBuilder StringBuffer
  4. 4. 常见的关于String、StringBuffer的面试题

参考链接

String 的修改原理

String的修改其实就是new了一个StringBuilder并调用append方法,然后在调用toString返回一个新的String。(注意:append方法并不会new一个新的对象)

String类的了解

查看String源码,发现String类是被final修饰的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private final char value[];

/** The offset is the first index of the storage that is used. */
private final int offset;

/** The count is the number of characters in the String. */
private final int count;

/** Cache the hash code for the string */
private int hash; // Default to 0

/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;

......

}

 从上面可以看出几点:

  1)String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。

​ 2) 从上面的三个方法可以看出,无论是sub操、concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。

深入理解String StringBuilder StringBuffer

  1. String str=”hello world”和String str=new String(“hello world”)的区别

    JVM内存机制:在class文件中有一部分来存储编译期间生成的字面常量和符号引用,这部分叫做class文件的常量池。在运行期间对应着方法区运行时的常量池。

     因此在上述代码中,String str1 = “hello world”;和String str3 = “hello world”; 都在编译期间生成了 字面常量和符号引用,运行期间字面常量”hello world”被存储在运行时常量池(当然只保存了一份)。通过这种方式来将String对象跟引用绑定的话,JVM执行引擎会先在运行时常量池查找是否存在相同的字面常量,如果存在,则直接将引用指向已经存在的字面常量;否则在运行时常量池开辟一个空间来存储该字面常量,并将引用指向该字面常量。

      总所周知,通过new关键字来生成对象是在堆区进行的,而在堆区进行对象生成的过程是不会去检测该对象是否已经存在的。因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容是相同的

    2.String StringBuilder StringBuffer的区别:

    String的修改其实就是new了一个StringBuilder并调用append方法,然后在调用toString返回一个新的String。

     那么有人会问既然有了StringBuilder类,为什么还需要StringBuffer类?查看源代码便一目了然,事实上,StringBuilder和StringBuffer类拥有的成员属性以及成员方法基本相同,区别是StringBuffer类的成员方法前面多了一个关键字:synchronized,不用多说,这个关键字是在多线程访问时起到安全保护作用的,也就是说StringBuffer是线程安全的。

     1)对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如”I”+”love”+”java”; 的字符串相加,在编译期间便被优化成了”Ilovejava”。这个可以用javap -c命令反编译生成的class文件进行验证。

      对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。

    2)String、StringBuilder、StringBuffer三者的执行效率:

      StringBuilder > StringBuffer > String

     当然这个是相对的,不一定在所有情况下都是这样。

      比如String str = “hello”+ “world”的效率就比 StringBuilder st = new StringBuilder().append(“hello”).append(“world”)要高。

      因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:

      当字符串相加操作或者改动较少的情况下,建议使用 String str=”hello”这种形式;

      当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。

常见的关于String、StringBuffer的面试题

下面是一些常见的关于String、StringBuffer的一些面试笔试题,若有不正之处,请谅解和批评指正。

  1. 下面这段代码的输出结果是什么?

  String a = “hello2”;   String b = “hello” + 2;   System.out.println((a == b));

  输出结果为:true。原因很简单,”hello”+2在编译期间就已经被优化成”hello2”,因此在运行期间,变量a和变量b指向的是同一个对象。

2.下面这段代码的输出结果是什么?

  String a = “hello2”;   String b = “hello”; String c = b + 2; System.out.println((a == c));

  输出结果为:false。由于有符号引用的存在,所以 String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的,因此这种方式生成的对象事实上是保存在堆上的。因此a和c指向的并不是同一个对象.

3.下面这段代码的输出结果是什么?

  String a = “hello2”;   final String b = “hello”; String c = b + 2; System.out.println((a == c));

  输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对final变量的访问在编译期间都会直接被替代为真实的值。那么String c = b + 2;在编译期间就会被优化成:String c = “hello” + 2;

4.下面这段代码输出结果为:

1
`public` `class` `Main {``    ``public` `static` `void` `main(String[] args) {``        ``String a = ``"hello2"``;``        ``final` `String b = getHello();``        ``String c = b + ``2``;``        ``System.out.println((a == c));``    ``}``    ` `    ``public` `static` `String getHello() {``        ``return` `"hello"``;``    ``}``}`

  输出结果为false。这里面虽然将b用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此a和c指向的不是同一个对象。

5.下面这段代码的输出结果是什么?

1
`public` `class` `Main {``    ``public` `static` `void` `main(String[] args) {``        ``String a = ``"hello"``;``        ``String b =  ``new` `String(``"hello"``);``        ``String c =  ``new` `String(``"hello"``);``        ``String d = b.intern();``        ` `        ``System.out.println(a==b);``        ``System.out.println(b==c);``        ``System.out.println(b==d);``        ``System.out.println(a==d);``    ``}``}`

  输出结果为(JDK版本 JDK6):

  img

  这里面涉及到的是String.intern方法的使用。在String类中,intern方法是一个本地方法,在JAVA SE6之前,intern方法会在运行时常量池中查找是否存在内容相同的字符串,如果存在则返回指向该字符串的引用,如果不存在,则会将该字符串入池,并返回一个指向该字符串的引用。因此,a和d指向的是同一个对象。

7.下面这段代码1)和2)的区别是什么?

1
`public` `class` `Main {``    ``public` `static` `void` `main(String[] args) {``        ``String str1 = ``"I"``;``        ``//str1 += "love"+"java";        1)``        ``str1 = str1+``"love"``+``"java"``;      ``//2)``        ` `    ``}``}`

  1)的效率比2)的效率要高,1)中的”love”+”java”在编译期间会被优化成”lovejava”,而2)中的不会被优化

Contents
  1. 1. String 的修改原理
  2. 2. String类的了解
  3. 3. 深入理解String StringBuilder StringBuffer
  4. 4. 常见的关于String、StringBuffer的面试题