signed

QiShunwang

“诚信为本、客户至上”

Java String详解、String原理、StringBuilder和StringBuffer的区别

2021/5/15 0:46:34   来源:

String

equals方法

如果参数是String则比较内容,否则比较地址;

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

String = "xxx"原理

String字符串常量

String的值是不可变;String的值是不可变;String的值是不可变;

        String str = "hello";
        System.out.println(str.hashCode()); //99162322
        str = str + "world" ;
        System.out.println("world".hashCode()); //113318802
        System.out.println(str.hashCode()); //-1524582912
        String str2 = "world";
        System.out.println(str2.hashCode());    //-1524582912

每次给String类型变量重新赋值的时,都会改变内存地址;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Prp6tLZJ-1621010680552)(https://note.youdao.com/yws/res/79167/231248494D9E4EE18C82A3A8410860E5)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cltCf7Od-1621010680564)(https://note.youdao.com/yws/res/79168/3112BF1179904895A474199F72DA6DA7)]
字符串常量池中的字符串会复用,在字符串"abc"已经入池的情况下,str2 直接指向之前"abc"而不用重新开辟内存;

但是new String(String str) 会重新开辟内存,即字符串 不会入 常量池;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eppOjJX3-1621010680566)(https://note.youdao.com/yws/res/79169/4B4D3351E1A2435E9A910116CE9FAF32)]

intern()

调用intern(),会使new String(String str) 入常量池,并返回指向常量池的引用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWJA6N62-1621010680568)(https://note.youdao.com/yws/res/79166/883722F10F624D4EB6F4B1AB7905F1F9)]

String a = new String("zhangsan");//"zhangsan"在堆内存中
String b = a.intern();//此时"zhangsan"进入常量池,b指向常量池中的"zhangsan"
String c = "zhangsan";//常量池中有“zhangsan”,故a指向常量池中的"zhangsan"

System.out.println(a == c);//false ,a指向堆内存,c指向从常量池
System.out.println(b == c);//true ,b,c都指向池中"zhangsan"

String 为什么不可变

https://blog.csdn.net/xiezhi_1130/article/details/84953379

String 的char数组是final修饰的,且内部不提供修改数组的方法

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    private final char value[];
 
    private int hash; // Default to 0
 
    private static final long serialVersionUID = -6849794470754667710L;
    
    ......此处省略N多代码
    
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
 
}

即使是其它的方法,返回的也不是同一个对象;

    public static void main(String[] args) {
       String s1 = new String("s");
       String s2 = s1.replace("s","a");

       System.out.println(s1==s2);  //false
       
       String s3 = s1;
       System.out.println(s1==s3);  //true
    }

字符串拼接原理

通过查看字节码

	String str = "abc";
	str = str + "de";

编译器会自动创建一个临时StringBuider类,然后使用append方法 依次append(str),append(de);最后toString 给str;

即 反编译后应该是这样的
	String str = "abc";
	StringBuider tmp =  new StringBuider(str);
	tmp.append("de");
	str= tmp.toString();

String str = “abc” 与 new String(“abc”)区别

注意,String str = “” 与 new String() 2者都是创建一个String对象,但是区别很大;

String str = “abc”

String str = “abc"实现过程:首先栈区创建str引用,然后在常量池池中是否有“abc”,若有则str指向"abc",若没有则创建一个;

也就是说

String str = "abc"
String str2 = "abc"
str==str2,即 指向常量池中相同内存地址;

String str = new String(“abc”)

String str = new String(“abc”)的实现过程:先检查常量池中是否存在"abc",若不存在,则先在常量池中创建”abc“,再执行new过程,对象像和普通的引用对象一样,分配在堆中,str指向堆。

如果 常量池中存在”abc“,那么直接执行new操作,在堆中分配内存,str指向堆中的对象;

因此有

String str = new String("abc")
String str2 = new String("abc")
str != str2 //==比较的是地址
但是str.equals(str2)却是ture,equals比较的是内容;

StringBuffer 和 StringBuilder 类

StringBuffer字符串变量、StringBuilder字符串变量

和 String 类不同的是,StringBuffer和StringBuilder类的对象能够被多次的修改,并且不产生新的未使用对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4PyjODwS-1621010680570)(https://note.youdao.com/yws/res/79170/69BBB40373B74909B5071686DBB10353)]

StringBuilder

构造函数

StringBuilder stringBuilder = new StringBuilder();  //如果不指定初始容量,则默认容量为16; 
StringBuilder stringBuilder1 = new StringBuilder(10);   //指定容量;
StringBuilder stringBuilder2 = new StringBuilder("asd");

参数为String的构造器源码
    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }

在参数Str 数组长度的基础上再增加16个字符长度,作为StringBuilder实例的初始数组容量,并将str字符串 append到StringBuilder的数组中。

StringBuffer

除了append方法,其它与StringBuilder基本一致;

append方法

可以看到,添加了synchronized同步锁;

@Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

总结

https://www.jianshu.com/p/64519f1b1137

String 类不可变,内部维护的char[] 数组长度不可变,为final修饰不存在扩容
字符串拼接,截取,都会生成一个新的对象。
频繁操作字符串效率低下,因为每次都会生成新的对象。

StringBuilder 类内部维护可变长度char[] , 初始化数组容量为16存在扩容非线程安全的字符串操作类, 其每次调用 toString方法而重新生成的String对象

StringBuffer 类内部维护可变长度char[], 基本上与StringBuilder一致,但其为线程安全的字符串操作类,大部分方法都采用了Synchronized关键字修改,以此来实现在多线程下的操作字符串的安全性。其toString方法而重新生成的String对象。

使用场景

1.使用String类的场景:在字符串不经常变化的场景中可以使用String类,例如常量的声明、少量的变量运算。

2.使用StringBuffer类的场景:在频繁进行字符串运算(如拼接、替换、删除等),并且运行在多线程环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装。

3.使用StringBuilder类的场景:在频繁进行字符串运算(如拼接、替换、和删除等),并且运行在单线程的环境中,则可以考虑使用StringBuilder,如SQL语句的拼装、JSON封装等。

性能

StringBuilder > StringBuffer > String