字符串相关类

45 阅读4分钟

字符串的相关类: String、StringBuilder、StringBuffer

String代表不可变的字符序列

StringBuffer类 和 StringBuilder类代表可变字符序列

这三类用法在开发中经常用到,必须掌握好。

String源码分析

String类对象代表不可变的Unicode字符序列,因此我们可以将对象称为不可变对象。那什么叫做不可变对象呢 ?指的是内部成员变量的值无法改变。源码如下:

image.png

我们发现字符串内容全部存储到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对象添加字符序列,仍然返回自身对象

image.png

  • StringBuilder 的delete(int start, int end) 可以删除从start开始到end-1为止的一段字符序列,仍然返回自身对象本身

image.png

  • StringBuilder 的deleteCharAt(int index) 移除此序列指定位置上的char,仍然返回自身对象本身

image.png

  • 重载的StringBuilder 的insert(...)方法,可以为该对象在指定位置插入字符序列,任然返回自身对象本身

image.png

  • StringBuilder 的reverse()用于将字符序列,仍然返回自身对象

image.png

  • 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));
    }
}

image.png