Java学习笔记之String、StringBuffer和StringBuilder

UML类关系图

基本概念

  • String:此类代表字符串常量,它们的值在创建之后不能更改。
  • StringBuffer:是一个线程安全的可变字符序列,它与String一样,在内存中保存的都是一个有序的字符串序列(char类型的数组),不同点是StringBuffer对象的值是可变的。
  • StringBuilder:与StringBuffer类基本相同,都是可变字符串系列,不同点是StringBuilder是线程不安全的。

分析

  1. 简单的说,String类型和StringBuilderStringBuffer类型的主要性能区别在于String是不可变的对象。这是由于,每次对String类型进行改变的时候,其实都等同于生成了一个新的String对象,然后将指针指向新的String对象。所以经常改变内容的字符串最好不要用String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。
  2. StringBuffer类型进行改变时,就是对其对象本身进行改变,而不是生成新的对象,再改变对象引用。在一般情况下,推荐使用StringBuffer,特别是字符串对象经常改变的情况下。
  3. StringBuffer在程序中可将字符串缓冲区安全地用于多线程,而且在必要时可以对这些方法进行同步。而StringBuilder的使用场景是单线程,不保证同步,该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用时。在这种情况下,它比StringBuffer要快。

equal 和 ==

==用于在比较两个对象的时候,检查两个引用是否指向了同一块内存。
equals()object的方法,默认情况下,它与==一样,比较的地址。但是当equal被重载之后,根据设计,equal 会比较对象的value。而这个是java希望有的功能。String 类就重写了这个方法。
例1:

1
2
3
4
5
6
String 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
6
String obj1 = new String("hello");
String obj2 = new String("hello");
if(obj1.equals(obj2))
System.out.println(true);
else
System.out.println(false);

输出为true

例3:obj1obj2指向同一块内存

1
2
3
4
5
6
String 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
6
String 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
6
String 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
16
public 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
14
public 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
*/

说明:

  1. 直接赋值的字符串常量(String s = “Hello World”)是存放于栈内存中,而通过new而来的字符串对象(String s = new String(“Hello World”))是存放于堆内存中的。则b,c,MESSAGE变量存放于栈内存中
  2. 对于字符串常量的相加,考虑到编译器优化问题,在编译时直接将字符串合并,而不是等到运行时再合并。即:String a = "tao" + "bao";String a = "taobao";编译出的字节码是一样的,它们都存在于栈内存中。
  3. 对于后面的(b + c),只能在运行时才会对其进行操作,而不是在编译时就进行合并。Java中的字符串相加是通过StringBuffer实现的。其存在于堆内存中。

CharSequence源码分析(基于jdk1.7.79)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package java.lang;
public interface CharSequence {
/**
* 返回字符序列的长度
*/

int length();

/**
* 返回指定索引的char值
*/

char charAt(int index);

/**
* 返回该序列的子序列
* @param start 开始位置的索引值(包括)
* @param end 结束位置的索引值(不包括)
* 说明:当start=end时,返回一个空序列
*/

CharSequence subSequence(int start, int end);

/**
* 返回一个包含该序列中字符的字符串,该字符串与序列的顺序相同
*/

public String toString();
}

StringBuilder源码分析(基于jdk1.7.79)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
package java.lang;

public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{


/** use serialVersionUID for interoperability */
static final long serialVersionUID = 4383685877147921099L;

/**
* 构造一个不带任何字符的字符串生成器,其初始容量为16个字符
*/

public StringBuilder() {
super(16);
}

/**
* 构造一个不带任何字符的字符串生成器,其初始容量由capacity参数设定
* 说明:其capacity参数值不能小于0,否则会抛出异常NegativeArraySizeException
*/

public StringBuilder(int capacity) {
super(capacity);
}

/**
* 构造一个字符串生成器,并初始化指定的字符串内容。其初始容量为指定字符串的
* 长度加上16
* 说明:其str不能为null,否则会抛出异常NullPointerException
*/

public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}

/**
* 构造一个字符串生成器,其包含与指定的字符序列相同的字符。其初始容量为字符
* 序列的长度加上16
* 说明:其seq不能为null,否则会抛出异常NullPointerException
*/

public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}

/**
* 追加Object参数的字符串表示形式到此序列
* 说明:如果obj为null,则追加"null";如果obj不为null,则追加obj.toString()
*/

public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}

/**
* 将指定的字符串追加到此序列
* 说明:如果str为null,则追加"null"
*/

public StringBuilder append(String str) {
super.append(str);
return this;
}

/** 将指定的StringBuilder参数追加到此序列 */
private StringBuilder append(StringBuilder sb) {
if (sb == null) // 如果sb为null,则追加"null"
return append("null");
int len = sb.length();
int newcount = count + len; // count为之前的字符长度
if (newcount > value.length) // 若新的字符长度newcount大于存储容量
expandCapacity(newcount); // 则进行扩容
sb.getChars(0, len, value, count); // 将sb复制到原字符序列的尾部
count = newcount; // 更新字符长度
return this;
}

/** 将指定的StringBuffer追加到此序列 */
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}

/** 将指定的CharSequence追加到此序列 */
public StringBuilder append(CharSequence s) {
if (s == null)
s = "null";
if (s instanceof String)
return this.append((String)s);
if (s instanceof StringBuffer)
return this.append((StringBuffer)s);
if (s instanceof StringBuilder)
return this.append((StringBuilder)s);
return this.append(s, 0, s.length());
}

/** 将指定的CharSequence子序列追加到此序列 */
public StringBuilder append(CharSequence s, int start, int end) {
super.append(s, start, end);
return this;
}

/** 将char数组参数的字符串表示形式追加到此序列 */
public StringBuilder append(char[] str) {
super.append(str);
return this;
}

/** 将char数组参数的子数组的字符串表示形式追加到此序列 */
public StringBuilder append(char[] str, int offset, int len) {
super.append(str, offset, len);
return this;
}

/**
* 将boolean参数的字符串表示形式追加到此序列
* 说明:若b为true,则追加"true";若b为false,则追加"false"
*/

public StringBuilder append(boolean b) {
super.append(b);
return this;
}

/** 将char字符的字符串表示形式追加到此序列 */
public StringBuilder append(char c) {
super.append(c);
return this;
}

/** 将int参数的字符串表示形式追加到此序列 */
public StringBuilder append(int i) {
super.append(i);
return this;
}

/** 将int参数的字符串表示形式追加到此序列 */
public StringBuilder append(long lng) {
super.append(lng);
return this;
}

/** 将float参数的字符串表示形式追加到此序列 */
public StringBuilder append(float f) {
super.append(f);
return this;
}

/** 将double参数的字符串表示形式追加到此序列 */
public StringBuilder append(double d) {
super.append(d);
return this;
}

/** 将codePoint参数的字符串表示形式追加到此序列 */
public StringBuilder appendCodePoint(int codePoint) {
super.appendCodePoint(codePoint);
return this;
}

/**
* 移除此序列的子字符串中的字符
* 说明:包含起始索引但不包括结束索引
*/

public StringBuilder delete(int start, int end) {
super.delete(start, end);
return this;
}

/** 移除此序列指定位置上的字符 */
public StringBuilder deleteCharAt(int index) {
super.deleteCharAt(index);
return this;
}

/**
* 使用给定的String中的字符替换此序列的子字符串中的字符
* 该子字符串从指定的start处开始,一直到索引end-1处的字符
*/

public StringBuilder replace(int start, int end, String str) {
super.replace(start, end, str);
return this;
}

/**
* 将str数组参数的子数组的字符串表示形式插入到此序列中
* 子数组从指定的offset(包括offset)开始,包括len个char
* 子数组的字符将被插入到index所指示的位置
*/

public StringBuilder insert(int index, char[] str, int offset,
int len)

{

super.insert(index, str, offset, len);
return this;
}

/** 将Object参数的字符串表示形式插入到此字符序列中 */
public StringBuilder insert(int offset, Object obj) {
return insert(offset, String.valueOf(obj));
}

/** 将字符串插入到此序列 */
public StringBuilder insert(int offset, String str) {
super.insert(offset, str);
return this;
}

/** 将char数组参数的字符串表示形式插入此序列中 */
public StringBuilder insert(int offset, char[] str) {
super.insert(offset, str);
return this;
}

/** 将指定的CharSequence插入此序列中 */
public StringBuilder insert(int dstOffset, CharSequence s) {
if (s == null)
s = "null";
if (s instanceof String)
return this.insert(dstOffset, (String)s);
return this.insert(dstOffset, s, 0, s.length());
}

/** 将指定 CharSequence 的子序列插入此序列中 */
public StringBuilder insert(int dstOffset, CharSequence s,
int start, int end)

{

super.insert(dstOffset, s, start, end);
return this;
}

/** 将 boolean 参数的字符串表示形式插入此序列中 */
public StringBuilder insert(int offset, boolean b) {
super.insert(offset, b);
return this;
}

/** 将 char 参数的字符串表示形式插入此序列中 */
public StringBuilder insert(int offset, char c) {
super.insert(offset, c);
return this;
}

/** 将 int 参数的字符串表示形式插入此序列中 */
public StringBuilder insert(int offset, int i) {
return insert(offset, String.valueOf(i));
}

/** 将 long 参数的字符串表示形式插入此序列中 */
public StringBuilder insert(int offset, long l) {
return insert(offset, String.valueOf(l));
}

/** 将 float 参数的字符串表示形式插入此序列中 */
public StringBuilder insert(int offset, float f) {
return insert(offset, String.valueOf(f));
}

/** 将 double 参数的字符串表示形式插入此序列中 */
public StringBuilder insert(int offset, double d) {
return insert(offset, String.valueOf(d));
}

/** 返回指定子字符串在此字符串中第一次出现处的索引 */
public int indexOf(String str) {
return indexOf(str, 0);
}

/** 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始*/
public int indexOf(String str, int fromIndex) {
return String.indexOf(value, 0, count,
str.toCharArray(), 0, str.length(), fromIndex);
}

/** 返回最右边出现的指定子字符串在此字符串中的索引 */
public int lastIndexOf(String str) {
return lastIndexOf(str, count);
}

/** 从指定位置开始,返回最右边出现的指定子字符串在此字符串中的索引 */
public int lastIndexOf(String str, int fromIndex) {
return String.lastIndexOf(value, 0, count,
str.toCharArray(), 0, str.length(), fromIndex);
}

/** 将此字符序列用其反转形式取代 */
public StringBuilder reverse() {
super.reverse();
return this;
}

/** 返回此序列中数据的字符串表示形式 */
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}

/**
* Save the state of the <tt>StringBuilder</tt> instance to a stream
* (that is, serialize it).
*
* @serialData the number of characters currently stored in the string
* builder (<tt>int</tt>), followed by the characters in the
* string builder (<tt>char[]</tt>). The length of the
* <tt>char</tt> array may be greater than the number of
* characters currently stored in the string builder, in which
* case extra characters are ignored.
*/

private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {

s.defaultWriteObject();
s.writeInt(count);
s.writeObject(value);
}

/**
* readObject is called to restore the state of the StringBuffer from
* a stream.
*/

private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {

s.defaultReadObject();
count = s.readInt();
value = (char[]) s.readObject();
}

}

面试中的相关问题

1.运算符+=
假设String str = "hello world";那么以下语句都是合法的

1
2
3
4
5
6
str += 100;    // 后加int型数值
str += 1000L; // 后加long型数值
str += 12.3; // 后加double型数值
str += 12.3f; // 后加float型数值
str += 'a'; // 后加char型数值
str += true; // 后加boolean型数值

但是特别注意:下面语句是不合法的

1
2
3
str += " a";      // 可行
//str += ' a'; // Invalid character constant,a前面不能有空字符
//str = 100; // Type mismatch: cannot convert from int to String