1. == 和 equals()

public class App {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "Hello";
        String s3 = s2;

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

        //
        String s4 = new String("Hello");
        String s5 = new String("Hello");
        System.out.println(s4 == s5);       // false
        System.out.println(s4.equals(s5));  // false
    }
}

解析:
先看第3句代码:"Hello"是字符串字面常量, 也是一个String类型的对象, Java编译器在常量池中分配空间并存储字符序列, 然后将该对象的引用赋值给变量str1;
再看第4句代码, "Hello"是字符串字面常量, Java编译器发现在常量池中已经存在该对象, 于是直接将该对象的引用赋值给变量str2, 换句换说, 编译器会把在程序中出现的相同内容的字符串字面量视作为同一个String对象。

JAVA string 頧柙son撖寡情_System


最后的str4和str5是在堆上开辟的空间,str4和str5指向的是不同的对象。

JAVA string 頧柙son撖寡情_Java_02

==运算符用于比较两个引用类型的变量时, 比较的是它们的引用值是否相等。
可以理解为引用的对象的地址是否相等,就上述来说,s1,s2,s3都指向对象"Hello",所以它们互等。但是对于s4和s5,它们分别指向不同的对象,两个这两个对象的地址不同,所以最终s4和s5不相等。尽管s4和s5从内存角度上来看不相等,但是s4和s5字符串的内容都是"Hello",所以s4.equals(s5)是true。

Java中,没法获取对象的地址,这要是在C++中,可以通过&取地址符来清晰的观察各个对象的内存地址。当然,Java中的String在内存和C++中std::string内存上的设计还是不同的。

// 看以下C++代码, 实际上 text1和text2的内存地址并不一样。
std::string  text1 = "xxx";
std::string  text2 = "xxx";

qDebug() << &text1;
qDebug() << &text2;

2. 从C++内存角度来看Java中的String

String 类是不可改变的,所以一旦创建了 String 对象,那它的值就无法改变了。该怎么理解这句话???

public class App {
    public static void main(String[] args) {
        String str = "Hello";
        System.out.println(str);

        str = "World";
        System.out.println(str);

    }
}

从结果上看是改变了,但为什么说 String 对象是不可变的呢?

  原因在于实例中的 str 只是一个 String 对象的引用,并不是对象本身,当执行 str = "World"; 创建了一个新的对象 "World",而原来的 "Hello" 还存在于内存中

JAVA string 頧柙son撖寡情_java_03


但是我们从C++的角度来看,完全又不一样了。

std::string text1 = "xxx";
std::cout << &text1 << "  " << * &text1 << std::endl;
text1 = "aaa";
std::cout << &text1 << "  " << * &text1 << std::endl;

JAVA string 頧柙son撖寡情_Java_04

JAVA string 頧柙son撖寡情_System_05


两次都是指向同一块内存空间,只不过是内存中值的改变罢了。原来的Hello被新值World给覆盖了而已。

3. StringBuffer 和 StringBuilder

// 这里并不是修改了str所指向的对象的内容, 而是改变了str变量的引用值,
// 让它指向了新的字符串对象"World".
String str = "Hello";
str = "World";

(1) StringBuffer:根据之前描述知道,如果想创建一个内容改变的字符串对象,那么String类显然是不满足要求的,这是可以考虑StringBuffer和StringBuilde两个类。StringBuffer是线程安全的,其内部使用了同步机制,导致在调用方法时,频繁地加锁和解锁,影响了执行效率。

(2)StringBuilder:非线程安全。如果不存在线程安全的问题,那么使用StringBuilder可以提高字符串的访问效率。

(3) 基本使用原则
如果要操作少量的数据用 String ;
单线程操作大量数据用StringBuilder ;
多线程操作大量数据,用StringBuffer。