基本概念
String
:此类代表字符串常量,它们的值在创建之后不能更改。StringBuffer
:是一个线程安全的可变字符序列,它与String
一样,在内存中保存的都是一个有序的字符串序列(char
类型的数组),不同点是StringBuffer
对象的值是可变的。StringBuilder
:与StringBuffer
类基本相同,都是可变字符串系列,不同点是StringBuilder
是线程不安全的。
分析
- 简单的说,
String
类型和StringBuilder
、StringBuffer
类型的主要性能区别在于String
是不可变的对象。这是由于,每次对String
类型进行改变的时候,其实都等同于生成了一个新的String
对象,然后将指针指向新的String
对象。所以经常改变内容的字符串最好不要用String
,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。 - 对
StringBuffer
类型进行改变时,就是对其对象本身进行改变,而不是生成新的对象,再改变对象引用。在一般情况下,推荐使用StringBuffer
,特别是字符串对象经常改变的情况下。 StringBuffer
在程序中可将字符串缓冲区安全地用于多线程,而且在必要时可以对这些方法进行同步。而StringBuilder
的使用场景是单线程,不保证同步,该类被设计用作StringBuffer
的一个简易替换,用在字符串缓冲区被单个线程使用时。在这种情况下,它比StringBuffer
要快。
equal 和 ==
==
用于在比较两个对象的时候,检查两个引用是否指向了同一块内存。equals()
是object
的方法,默认情况下,它与==
一样,比较的地址。但是当equal
被重载之后,根据设计,equal
会比较对象的value
。而这个是java
希望有的功能。String
类就重写了这个方法。
例1:1
2
3
4
5
6String obj1 = new String("hello");
String obj2 = new String("hello");
if(obj1 == obj2)
System.out.println(true);
else
System.out.println(false);
输出为false
。
例2:1
2
3
4
5
6String obj1 = new String("hello");
String obj2 = new String("hello");
if(obj1.equals(obj2))
System.out.println(true);
else
System.out.println(false);
输出为true
。
例3:obj1
与obj2
指向同一块内存1
2
3
4
5
6String obj1 = new String("hello");
String obj2 = obj1;
if(obj1 == obj2)
System.out.println(true);
else
System.out.println(false);
输出为true
。
例4:一种特殊情况,直接赋值的字符串常量(不是通过new)1
2
3
4
5
6String obj1 = "hello";
String obj2 = "hello";
if(obj1 == obj2)
System.out.println(true);
else
System.out.println(false);
输出为true
。这是因为程序在运行的时候会创建一个字符串缓冲池(也就是栈内存)。当使用 String obj1 = "hello";
这样的表达是创建字符串的时候(非new
这种方式),程序首先会在这个 String
缓冲池中寻找相同值的对象,如果这样的对象不存在,则会创建这个字符串对象,然后将刚创建的对象的引用放入字符串缓冲池中,并将引用返回给变量obj1
;当执行String obj2 = "hello";
语句时,JVM同样会字符串缓冲池中查找是否有相同值的对象存在,如果有就将该对象引用返回给obj2
,故它们指向同一个对象。
例5:new出来的字符串对象1
2
3
4
5
6String obj1 = new String("hello");
String obj2 = "hello";
if(obj1 == obj2)
System.out.println(true);
else
System.out.println(false);
输出为false
。当我们使用new
来构造字符串对象的时候,不管字符串缓冲池有没有相同内容的对象的引用,新的字符串对象都会创建。故它们是所指向的对象是不同的。
注意:使用new
来构造字符串对象时,如果想将这个对象的引用加入到字符串缓冲池,可以使用intern
方法。具体看参考String常用方法总结
在Java中,对象都创建在堆内存中。
例6:字符串操作符+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Test {
public static void main(String[] args) {
int a = 1;
int b = 2;
System.out.println( a + b + "");
System.out.println("" + a + b );
System.out.println( a + b + "" + a + b);
System.out.println("" + ( a + b ));
}
}
/** output
3
12
312
3
*/
第一个输出语句:先进行整数求和运算,再将运算结果转换成String输出;
第二个输出语句:由于第一个变量类型为String,后面的变量都得转换成String,然后进行String组合,而不是先进行整数求和运算;
第三个输出语句:由于第一个变量类型不是String,所以先进行求和运算,然后遇到了String类型,运算结果要进行转换,并且后面的int也需要进行转换;
第四个输出语句:虽然第一个变量类型为String,但是后面的括号控制了表达式的赋值顺序,以使得int类型的变量在显示之前确实进行了求和操作。
例7:关于编译器优化1
2
3
4
5
6
7
8
9
10
11
12
13
14public class StringDemo {
private static final String MESSAGE = "taobao";
public static void main(String[] args) {
String a = "tao" + "bao";
String b = "tao";
String c = "bao";
System.out.println(a == MESSAGE);
System.out.println((b + c) == MESSAGE);
}
}
/** output
true
false
*/
说明:
- 直接赋值的字符串常量(String s = “Hello World”)是存放于栈内存中,而通过new而来的字符串对象(String s = new String(“Hello World”))是存放于堆内存中的。则b,c,MESSAGE变量存放于栈内存中
- 对于字符串常量的相加,考虑到编译器优化问题,在编译时直接将字符串合并,而不是等到运行时再合并。即:
String a = "tao" + "bao";
和String a = "taobao";
编译出的字节码是一样的,它们都存在于栈内存中。- 对于后面的
(b + c)
,只能在运行时才会对其进行操作,而不是在编译时就进行合并。Java中的字符串相加是通过StringBuffer
实现的。其存在于堆内存中。
CharSequence源码分析(基于jdk1.7.79)
1 | package java.lang; |
StringBuilder源码分析(基于jdk1.7.79)
1 | package java.lang; |
面试中的相关问题
1.运算符+=
假设String str = "hello world";
那么以下语句都是合法的1
2
3
4
5
6str += 100; // 后加int型数值
str += 1000L; // 后加long型数值
str += 12.3; // 后加double型数值
str += 12.3f; // 后加float型数值
str += 'a'; // 后加char型数值
str += true; // 后加boolean型数值
但是特别注意:下面语句是不合法的1
2
3str += " a"; // 可行
//str += ' a'; // Invalid character constant,a前面不能有空字符
//str = 100; // Type mismatch: cannot convert from int to String