String、StringBuffer、StringBuilder笔记

一、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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值