一、String字符串常量
String不可变
要理解String的不可变性,首先看一下String类中都有哪些成员变量。 在JDK1.6中,String的成员变量有以下几个:
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</string>
JDK1.7中String类的主要成员变量就剩下了两个:
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</string>
由以上的代码可以看出, 在Java中String类其实就是对字符数组的封装。JDK6中, value是String封装的数组,offset是String在这个value数组中的起始位置,count是String所占的字符的个数。在JDK7中,只有一个value变量,也就是value中的所有字符都是属于String这个对象的。
value,offset和count这三个变量都是private的,并且没有提供setValue, setOffset和setCount等公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这三个成员。此外,value,offset和count这三个变量都是final的, 也就是说在String类内部,一旦这三个值初始化了, 也不能被改变。所以可以认为String对象是不可变的了。
当创建一个String对象时,变量名会存放在栈中,String对象本身存放在堆中。
如下代码:
String s = "ABCabc";
System.out.println("s = " + s);
s = "123456";
System.out.println("s = " + s);
打印结果为: s = ABCabc
s = 123456
看似String是可变的,其实s只是一个引用,堆内部新创建了一个String对象,让s指向了新的String对象,达到了看似改变的效果。
再看如下代码:
String str1=new String("hello");
str1=str1+" world!";
System.out.println(str1);
输出结果为;hello world!
初始String值为“hello”,然后在这个字符串后面加上新的字符串“world”,这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了“hello world”字符串也相应的需要开辟内存空间,这样短短的两个字符串,却需要开辟三次内存空间,不得不说这是对内存空间的极大浪费。
二、StringBuffer (线程安全)和 StringBuilder (线程不安全)——StringBuffer、StringBuilder字符串变量
每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。但是在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:
String S1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
其实这是 JVM 的一个把戏,在 JVM 眼里,这个
String S1 = “This is only a” + “ simple” + “test”; 其实就是:
String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:
String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;
String S1 = S2 +S3 + S4;
这时候 JVM 会规规矩矩的按照原来的方式去做。
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
三、三者的区别
(1)字符修改上的区别(主要)
- String:不可变字符串;
- StringBuffer:可变字符串、效率低、线程安全;
- StringBuilder:可变字符序列、效率高、线程不安全;
(2)初始化上的区别,String可以空赋值,后者不行,报错
(3)小结:
- 如果要操作少量的数据用 String;
- 多线程操作字符串缓冲区下操作大量数据 StringBuffer;
- 单线程操作字符串缓冲区下操作大量数据 StringBuilder(推荐使用)。
四、 面试题
面试题1:String s = new String(“hello”)和String s = "hello"有区别吗?
有区别。前者会创建两个对象,后者创建一个对象。
面试题2:有关此处看程序写结果的问题?
答:(1)字符串如果是变量相加,先开空间,再拼接;
(2)字符串如果是常量相加,先加,之后在变量池找,如果有就直接返回,没有,就创建。
面试题3:StringBuffer和String区别?
StringBuffer长度和内容可变,String内容和长度不可变。如果使用StringBuffer做字符串的拼接,不会浪费太多的资源。
面试题4:String、StringBuffer、StringBuilder的区别?
(1)String是内容不可变的,而StringBuffer、StringBuilder都是内容可变的。
(2)StringBuffer是同步的,数据安全的,但是效率低; StringBuilder是不同步的,数据不安全,相比于来说,效率高。
面试题5:StringBuffer和数组的区别?
二者都是可以看成是一个容器,装其他的数据,但StringBuffer的数据最终是一个字符串数据;而数组可以放置多种数据,但必须是同一种数据类型的。
下面这段代码的输出结果是什么?
String a = "hello2";
String b = "hello" + 2;
System.out.println((a == b));
答:true。为什么呢?因为"hello" + 2在编译期间被优化成了"hello2",故运行期间a、b指向的是保存在常量池中的同一对象。
下面这段代码的输出结果是什么?
String a = "hello2";
String b = "hello";
String c = b + 2;
System.out.println((a == c));
答:false。由于有引用符号的存在,所有b + 2不会再编译期间被优化,不会吧b + 2当做字面常亮来处理,这种生成对象是保存在堆上。而a的对象是在常量池中。故a和c指向的并不是同一个对象。
下面这段代码的输出结果是什么?
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。
下面这段代码输出结果为:
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指向的不是同一个对象。
下面这段代码的输出结果是什么?
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);
}
}
false false false true。在String类中,intern方法(intern详解)是一个本地方法,在JAVA SE6之前,intern方法会在运行时常量池中查找是否存在内容相同的字符串,如果存在则返回指向该字符串的引用,如果不存在,则会将该字符串入池,并返回一个指向该字符串的引用。因此,a和d指向的是同一个对象。
参考链接:
原文链接:https://blog.csdn.net/weixin_38894058/article/details/79418005
原文链接:https://blog.csdn.net/weixin_41101173/article/details/79677982
原文链接:https://www.cnblogs.com/leskang/p/6110631.html
原文链接:https://blog.csdn.net/rmn190/article/details/1492013