首页 > 代码库 > Java学习笔记(1)String常见面试知识总结
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<String>, 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 <Class java.lang.String> 3 dup 4 ldc #2 <String "Stanford "> 6 invokespecial #12 <Method java.lang.String(java.lang.String)> 9 astore_1 10 new #8 <Class java.lang.StringBuffer> 13 dup 14 aload_1 15 invokestatic #23 <Method java.lang.String valueOf(java.lang.Object)> 18 invokespecial #13 <Method java.lang.StringBuffer(java.lang.String)> 21 ldc #1 <String "Lost!!"> 23 invokevirtual #15 <Method java.lang.StringBuffer append(java.lang.String)> 26 invokevirtual #22 <Method java.lang.String toString()> 29 astore_1
0 new #8 <Class java.lang.StringBuffer> 3 dup 4 ldc #2 <String "Stanford "> 6 invokespecial #13 <Method java.lang.StringBuffer(java.lang.String)> 9 astore_1 10 aload_1 11 ldc #1 <String "Lost!!"> 13 invokevirtual #15 <Method java.lang.StringBuffer append(java.lang.String)> 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学习笔记(1)String常见面试知识总结