Java字符串知多少:String、StringBuffer、StringBuilder

一、String

1、简介

  • String 是 Java 中使用得最频繁的一个类了,不管是作为开发者的业务使用,还是一些系统级别的字符使用, String 都发挥着重要的作用。
  • String 是不可变的、final的,不能被继承,且 Java 在运行时也保存了一个字符串池(String pool) ,就使得 String 变得很特殊。
  • 实现三个接口:java.io.Serializable, Comparable, CharSequence

2、String对象两种创建方法

// 1:编译时就确定了字符串的内容
String strComplier = "A";
// 2:new,只有运行时才能确定字符串的内容
String strNew = new String("A");
  • String strComplier = “A”;

Java 程序在运行的时候会维护着一个常量池,编译期生成的各种字面量和符号引用会在类加载后进入方法区的运行时常量池。对于上述这种实现字符串的方式就可以在编译的时候确定字符串的内容,因此这一行生成的内存结构就如下图。

Java字符串知多少:String、StringBuffer、StringBuilder_第1张图片

不严谨的讲:虚拟机栈中的 strComplier 存储的就是 A 在常量池中的地址

  • String strNew = new String(“A”);

因为使用的 new 的方式,所以这句代码只有运行的时候才能确定字符串的内容。而对于 new 关键字,java 是将对象的实例数据存放上的,但是又因 String 常量池的存在,因此实际上在堆上的 String 对象的数据又指向了字符串常量池。

Java字符串知多少:String、StringBuffer、StringBuilder_第2张图片

不严谨的讲:虚拟机栈中的 strNew 存储的就是 strNew 这个对象在堆内存的地址,而 strNew 中的字符串数据又指向了常量池中的 A

3、比较: == 和 equals()

  • ==

    比较两个对象的引用是否相等,也就是说比较两个地址是否相等

//true : 地址相同
String a = "A";
String a1 = "A";
System.out.println(a1 == a); //指向的都是 A 的地址,地址相同,返回的是 true
//false : 地址不同,在堆中是两个不同的对象,虽然指向常量池中的同一个值 A
String b = new String("A");
String b1 = new String("A");
System.out.println(b==b1);//分别指向的是在堆内存上的不同对象的地址,地址不同,返回的是 false
//false : 地址不同
String c = "A";
String c1 = new String("A");
System.out.println(c == c1);//一个指向常量池,一个指向堆,返回 false
//true : 地址相同
String d = "A";
String d1 = d; 
System.out.println(d == d1);//把d中的 常量池地址 赋给d1,返回 true
//true : 地址相同
String a = "A1";
String a1 = "A" + 1;//编译时已确定
System.out.println(a == a1);//true

//false : 地址不同
String b = "A1";
String b1 = "A";
String b2 = b1 + 1;//编译时不确定
System.out.println(b == b2);//false

Java字符串知多少:String、StringBuffer、StringBuilder_第3张图片

  • equals

    比较的两个对象是否相等,也就是说是同一个对象,在jvm堆内是惟一的

    String类对equals()方法进行了重写,只有值相等,才为true

//equals方法源码
public boolean equals(Object anObject) {
    //判断是否是相同对象
    if (this == anObject) {
        return true;
    }
    //判断anObject是否是String类型
    if (anObject instanceof String) {
        //        
        String aString = (String)anObject;
        //判断编码是否相同
        if (coder() == aString.coder()) {//
            //比较两个字符串是否完全相等
            return isLatin1() ? StringLatin1.equals(value, aString.value)                : StringUTF16.equals(value, aString.value);
        }
    } 
    return false;
}

Java字符串知多少:String、StringBuffer、StringBuilder_第4张图片

String stringCompiler = "A";
String stringNew = new String("A");
StringBuilder stringBuilder = new StringBuilder("A");
StringBuffer stringBuffer = new StringBuffer("A");
//不是相同对象
System.out.println(stringCompiler == stringNew);  // false
//不是相同对象,但都是String类型,编码、字符值都相同
System.out.println(stringCompiler.equals(stringNew));  // true
//stringBuilder不是String类型
System.out.println(stringCompiler.equals(stringBuilder));  // false
//stringBuffer不是String类型
System.out.println(stringNew.equals(stringBuffer));   // false
比较 == equals()
String s1 = new String(“java”);
String s2 = new String(“java”);
false
s1、s2在堆中是不同的对象,地址不同
虽然都指向了常量池中的’'java"
true
String类对equals()进行了重写
只要值相同,就返回true
String s1 = new String(“java”);
String s2 = s1;
true
同一对象,地址相同,值相同
true
同一对象,地址相同,值相同
String s1 = “java”;
String s2 = “java”;
true
编译时值已确定
指向常量池中同一地址
true
值相同

4、final

  • String 是 final ,也就是String 是不可变的。即一个 String 对象创建之后所有对它修改后的字符串,都是新生成的 String 对象。
  • String 设计成 final 主要有如下原因:
    • 实现字符串常量池,只有当字符串是不可变时字符串池才有可能实现,字符串池的实现可以在运行时节约很多 heap 空间,因为不同的字符串变量都指向池中的同一个字符串,即可以实现多个变量引用 JVM 内存中的同一个字符串实例,如果字符串不是不变的,String interning 将不能实现(String interning 是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
    • 安全问题,在系统中有很多地方都是以字符串的形式存在的,比如数据库的用户名,Socket 的主机和端口,当你在调用其他方法,比如调用一些系统级操作之前,可能会有一系列校验,如果是可变类的话,可能在你校验过后,其内部的值被改变了,可能引起严重的系统崩溃问题。
    • 缓存提升性能,String 大量运用在哈希的处理中,由于 String 的不可变性,可以只计算一次哈希值,然后缓存在内部,后续直接取就好了,字符串的处理速度要快过其它的键对象,这就是 HashMap 中的键往往都使用字符串的原因。
    • 线程安全,因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。

5、编码与解码

//编码
String.getBytes()//方法是得到一个操作系统默认的编码格式的字节数组。
String.getBytes(String decode)//方法会根据指定的decode编码返回某字符串在该编码下的byte数组表示

//解码    
new String(byte[] b,String decode)//按照指定的方法编码
  • 示例
//编码解码一致,正确输出
byte[] b_gbk = "中".getBytes("GBK"); 
String s_gbk = new String(b_gbk,"GBK"); 
System.out.println(s_gbk);//中
//编码解码不一致,输出乱码
byte[] b_gbk = "中".getBytes("GBK"); 
String s_utf8 = new String(b_gbk,"UTF-8"); 
System.out.println(s_utf8);//��

二、StringBuffer

  • final,不能被继承,不能有子类
  • 线程安全,方法有同步锁
  • 通过append、insert进行字符串的操作
  • 实现接口:java.io.Serializable, CharSequence

三、StringBuilder

  • final,不能被继承,不能有子类
  • 线程不安全,没有锁,且append方法不是原子操作
  • 通过append、insert进行字符串的操作
  • 实现接口:java.io.Serializable, CharSequence

四、三者比较

比较 String StringBuffer StringBuilder
继承性 final final final
实现接口 Serializable
Comparable
CarSequence
Serializable
CarSequence
Serializable
CarSequence
速度
线程安全性 线程安全 (final) 线程安全(有锁) 线程不安全
常用方法 equals() append()
insert()
append()
insert()
适用场景 少量的字符串操作 多线程下的大量操作 单线程下的大量操作

为什么 StringBuilder 不是线程安全的?

你可能感兴趣的:(Java,java,jvm,开发语言)