Java学习笔记(1)String常见面试知识总结



简介(摘自Java源码)

Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings. Because String objects are immutable they can be shared.

从这句话当中,我们可以知道String是一个不可变对象,对象的值不可以被改变。因为这一点,所以String是线程安全的。然后Stringbuffer是可变对象。

【问题1】什么是不可变对象(immutable object),不可变对象有什么好处,在什么情况下应该用,或者更具体一些,Java的String类为什么要设成immutable类型?

答:从String类的源码中,我们可以知道不可变对象是被final关键字修饰的类产生的对象,其成员变量也是被final修饰的,因此该类不能被修改也不能被继承。

不可变对象的好处主要体现在以下两方面:

1. 不可变对象是线程安全的,可用于多线程。在多线程通信中,某一个变量很可能被多个线程进行修改,因此是不安全的。而不可变对象不能被修改,安全;

2.不可变对象可以提高拷贝时的效率,不可变意味着拷贝时只需要拷贝地址,效率高。

public final class String
    implements java.io.Serializable, Comparable, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** 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;

...........................................
}
public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

    /**
     * A cache of the last value returned by toString. Cleared
     * whenever the StringBuffer is modified.
     */
    private transient char[] toStringCache;

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    static final long serialVersionUID = 3388685877147921107L;
}
而StringBuffer是继承了AbstractStringBuilder

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

    /**
     * The count is the number of characters used.
     */
    int count;
}
从上面一段代码中再次可以看出,String和StringBuffer的区别,String是不可变对象,StringBuffer是不可变对象

【问题2】String和StringBuffer以及StringBuilder的区别是什么?

从问题1中我们已经看到了其中一个主要的区别:String是不可变对象,StringBuffer是可变对象。这一点也导致了其他的很多的区别。

看下面两个例子字符串拼接:

String str = new String ("Stanford  ");
     str += "Lost!!";
StringBuffer str = new StringBuffer ("Stanford ");
     str.append("Lost!!");
问:哪个效率高?

乍一眼看过去还真以为第一种方式高于第二种方式,其实不然。我们看两张程序的字节码。

0 new #7 
3 dup 
4 ldc #2 
6 invokespecial #12 
9 astore_1
10 new #8 
13 dup
14 aload_1
15 invokestatic #23 
18 invokespecial #13 
21 ldc #1 
23 invokevirtual #15 
26 invokevirtual #22 
29 astore_1
0 new #8 
3 dup
4 ldc #2 
6 invokespecial #13 
9 astore_1
10 aload_1 
11 ldc #1 
13 invokevirtual #15 
16 pop
在第一种方式中,由于String是不可变对象,所以在第10行的时候,它将string对象转换成了StringBuffer对象,再把字符拼接上去,在29行的时候重新转换成String对象,返回给用户。而在第二种方式中,StringBuffer是可变对象,直接拼接字符串并返回。从这一点很显然看出,在常见的字符串拼接问题上,String的效率比StringBuffer低。


再说StringBuilder,StringBuilder和StringBuffer一样都是可变对象,那有什么区别呢?我们稍微比较一下源码就可以看到StringBuffer中的大多数函数都带有关键字:synchronized和StringBuilder中没有。因此,StringBuilder在单线程中效率高于StringBuffer,而StringBuffer可以用于多线程。

【问题3】String两种初始化方式的区别

String str = new String("abc");
String str = "abc";

 这是我们常见的两种String对象的初始化方式,区别何在?首先我们来看一段代码。 
  

String str1 = new String("abc"); //jvm 在堆上创建一个String对象   
  
 //jvm 在strings pool中找不到值为“abc”的字符串,因此   
 //在堆上创建一个String对象,并将该对象的引用加入至strings pool中   
 //此时堆上有两个String对象   
Stirng str2 = "abc";   
  
 if(str1 == str2){   
         System.out.println("str1 == str2");   
 }else{   
         System.out.println("str1 != str2");   
 }   
  //打印结果是 str1 != str2,因为它们是堆上两个不同的对象   
  
  String str3 = "abc";   
 //此时,jvm发现String Pool中已有“abc”对象了,因为“abc”equals “abc”   
 //因此直接返回str2指向的对象给str3,也就是说str2和str3是指向同一个对象的引用   
  if(str2 == str3){   
         System.out.println("str2 == str3");   
  }else{   
         System.out.println("str2 != str3");   
  }   
输出结果是什么?

再来看一段代码

String str1 = new String("abc"); //jvm 在堆上创建一个String对象   
  
str1 = str1.intern();   
//程序显式将str1放到String Pool中,intern运行过程是这样的:首先查看String Pool   
//有没“abc”对象的引用,没有,则在堆中新建一个对象,然后将新对象的引用加入至   
//String Pool中。执行完该语句后,str1原来指向的String对象已经成为垃圾对象了,随时会   
//被GC收集。   
  
//此时,jvm发现String Pool中已有“abc”对象了,因为“abc”equals “abc”   
//因此直接返回str1指向的对象给str2,也就是说str2和str1引用着同一个对象,   
//此时,堆上的有效对象只有一个。   
Stirng str2 = "abc";   
  
 if(str1 == str2){   
         System.out.println("str1 == str2");   
 }else{   
         System.out.println("str1 != str2");   
 }   

打印结果又是什么?

这个问题就涉及到Java的内存模型了,简单的说来区别在于:

第一种初始化方式会立即在对上创建一个String对象,然后将该对象的引用返回给用户。对于第二种,jvm首先会在String Pool判断是否存在该String对象。如果有,则返回已有的String对象,如果没有,则在heap中重新创建对象,将其引用返回给用户同时将该引用添加至String Pool中。而第一种方式是不会主动把对象添加至String Pool中的。因此第一段程序的输出结果是

str1 != str2
str1 == str2
那么第二段程序的输出结果呢?前面我们说到第一种方式是不会主动把对象添加至String Pool中的,但有个例外,手动调用intern()方法,会强制将该对象的引用加入到String Pool里面。因此第二段程序的结果是

str1 == str2

【问题4】String Pool和Const Pool的区别和联系

了解Java内存模型的都知道,在Java内存中有一块区域叫做方法区。方法区主要用于存储一些常量和静态变量。而String Pool和Const Pool的区别简单的说来,String Pool是Const Pool的一部分,Const Pool包括很多种常量,整型啊之类的,而String Pool只是用来专门存储String常量。

没有写完,持续更新。。。。




你可能感兴趣的:(java学习笔记,Java,String,不可变对象,初始化,StringBuffer)