字符串的相关类: String、StringBuilder、StringBuffer
String代表不可变的字符序列
StringBuffer类 和 StringBuilder类代表可变字符序列
这三类用法在开发中经常用到,必须掌握好。
String源码分析
String类对象代表不可变的Unicode字符序列,因此我们可以将对象称为不可变对象。那什么叫做不可变对象呢 ?指的是内部成员变量的值无法改变。源码如下:
我们发现字符串内容全部存储到value[]数组中,而变量value是final类型的,也就是常量(即只能被赋值一次)。这就是“不可变对象”的典型定义方式。
我们发现在前面学习String的某些方法,比如:substring()是对字符串的截取操作,但本质是读取原字符串内容生成了新的字符串。测试代码如下:
public class Test {
public static void main(String[] args) {
String a = "abc";
String b = a.substring(0,1);
System.out.println(a); // abc
System.out.println(b); // a
}
}
注意:在遇到字符串常量之间的拼接时,编译器会做出优化,即在编译期间就会完成字符串的拼接。因此,在使用==进行String对象之间的比较时,我们要特别注意,如示例所示:
// 编译器做了优化,编译的时候就已经拼接
String a = "hello" + "java";
String b = "hellojava";
System.out.println(a == b); // true
String c = "hello";
String d = "world";
// 编译器不知道 c 和 d是什么,所以不能提前拼接
String e = c + d;
System.out.println(b == d);
StringBuffer 和 StringBuilder 可变字符序列
StringBuffer 和 StringBuilder 都是可变字符序列:
- StringBuffer 线程安全,做线程同步检查,效率比较低
- StringBuilder 线程不安全,不做线程同步检查,因此效率比较高。建议用这个
常用方法列表
- 重载的StringBuilder 的append()可以为该StringBuilder对象添加字符序列,仍然返回自身对象
- StringBuilder 的delete(int start, int end) 可以删除从start开始到end-1为止的一段字符序列,仍然返回自身对象本身
- StringBuilder 的deleteCharAt(int index) 移除此序列指定位置上的char,仍然返回自身对象本身
- 重载的StringBuilder 的insert(...)方法,可以为该对象在指定位置插入字符序列,任然返回自身对象本身
- StringBuilder 的reverse()用于将字符序列,仍然返回自身对象
- StringBuilder 的toString()返回此序列中数据的字符串表示形式
public class Test {
public static void main(String[] args) {
StringBuilder str = new StringBuilder();
for(int i = 0; i < 7; i++) {
str.append((char)('a' + i)); // 追加单个字符
}
System.out.println(str.toString()); // "abcdefg"
str.append(", I can say abc"); // 追加字符串
System.out.println(str.toString()); // abcdefg, I can say abc
StringBuilder str1 = new StringBuilder("去上学");
str1.insert(0, "要").insert(0, "我"); // 插入字符串
System.out.println(str1.toString()); // 我要去上学
str1.delete(0, 1); // 删除字符串
System.out.println(str1.toString()); // 要去上学
str1.deleteCharAt(0).deleteCharAt(0); // 删除某个字符
System.out.println(str1.toString()); // 上学
str1.reverse(); // 逆序
System.out.println(str1.toString()); // 学上
}
}
不可变序列和可变序列的使用陷阱
- String的使用陷阱
String一经初始化后,就不会改变其内容了。对String字符串的操作实际上是对其副本的操作,原来的字符串一点没有变,比如:
public class Test {
public static void main(String[] args) {
String s = "a";
s = s + "b";
System.out.println(s);
}
}
实际上原来的"a"字符串对象已经丢弃了,现在又产生了另一个字符串s+"b"(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的时间和空间性能,甚至会造成服务器的崩溃。
StringBuilder和StringBuffer类是对原字符串本身操作的,可以对字符串进行修改而不产生副本拷贝或者产生少量的副本。因此可以在循环中使用。
示例: String类 和 StringBuilder类在字符串频繁修改时的效率测试
public class Test {
public static void main(String[] args) {
// 使用String 进行拼接
String s = "";
long num1 = Runtime.getRuntime().freeMemory(); // 获取系统剩余的内存空间
long time1 = System.currentTimeMillis(); // 获取系统的当前时间
for(int i = 0; i < 5000; i++) {
s = s + i; // 相当于产生了 5000 个对象
}
long num2 = Runtime.getRuntime().freeMemory(); // 获取系统剩余的内存空间
long time2 = System.currentTimeMillis(); // 获取系统的当前时间
System.out.println("String占用内存" + (num2 - num1));
System.out.println("String 耗时" + (time2 - time1));
// 使用StringBuilder 进行拼接
StringBuilder str = new StringBuilder();
long num3 = Runtime.getRuntime().freeMemory();
long time3 = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
str.append(i);
}
long num4 = Runtime.getRuntime().freeMemory();
long time4 = System.currentTimeMillis();
System.out.println("StringBuilder占用内存" + (num4 - num3));
System.out.println("StringBuilder耗时" + (time4 - time3));
}
}