Java 常用类之字符串

1、字符串的共享与常量折叠

String类的对象是不可变的,我们只能改变 String 类型的对象变量的值(即指针的指向),而不能改变对象本身。

字符串字面量是共享的,而用+拼接 String 对象变量或执行substring等操作得到的字符串不共享,验证一下:

public class Main {
    public static void main(String[] args) {
        String s1 = "He", s2 = "llo";
        System.out.println(s1 == "He" && s2 == "llo"); // true
        System.out.println("He" + "llo" == "Hello"); // true
        System.out.println("----------");
        System.out.println(s1 + "llo" == "Hello"); // false
        System.out.println(s1 + s2 == "Hello"); // false
        System.out.println("Hello World!".substring(0, 5) == "Hello"); // false
    }
}

没问题!但是为什么用 + 符号拼接的 String 对象常量是共享的呢?

我们接着看。如果为s1s2加上final修饰符,结果会有哪些变化:

public class Main {
    public static void main(String[] args) {
        final String s1 = "He",  s2 = "llo";
        System.out.println(s1 == "He" && s2 == "llo"); // true
        System.out.println("He" + "llo" == "Hello"); // true
        System.out.println("----------");
        System.out.println(s1 + "llo" == "Hello"); // true
        System.out.println(s1 + s2 == "Hello"); // true
        System.out.println("Hello World!".substring(0, 5) == "Hello"); // false
    }
}

如上所示,第三和第四个判断语句的结果与之前不同,结果为true。为什么会这样呢?

这是因为编译器有一种称为常量折叠的优化技术:它会在编译期间,完成常量之间的计算,而不需要等到运行时计算。

我们简化一下上述代码,再使用反编译命令javap -v Main.class,验证一下。

简化代码如下:

public class Main {
    public static void main(String[] args) {
        final String s1 = "He",  s2 = "llo";
        String a = s1 + "llo";
        String b = s1 + s2;
    }
}

反编译如下:

Code:
      stack=1, locals=5, args_size=1
         0: ldc           #7                  // String He
         2: astore_1
         3: ldc           #9                  // String llo
         5: astore_2
         6: ldc           #11                 // String Hello
         8: astore_3
         9: ldc           #11                 // String Hello
        11: astore        4
        13: return

根据ldc指令可以知道,字符串Hello都是从常量池中取的,没有进行+拼接操作,这就验证了编译器的常量折叠。

2、码点与代码单元

区分码点代码单元:一个 UTF-16 编码的 Unicode 字符对应一个码点,对应 1 ~ 2 个 char 值;一个 char 值对应一个代码单元。

如下所示,Unicode 字符🍺(啤酒杯)的码点为 U+1F37A;它对应两个代码单元,分别是 U+D83C 和 U+DF7A;它也对应两个 char 值,分别是 \uD83C 和 \uDF7A。

public class Main {
    public static void main(String[] args) {
        String s = "\uD83C\uDF7A"; // 🍺(啤酒杯)
        System.out.println(s); // 🍺
        System.out.println(Arrays.toString(s.toCharArray())); // [?, ?]
    }
}

平时我们都是对代码单元进行遍历,那么如何对码点进行遍历?如下所示。

public class Main {
    public static void main(String[] args) {
        String str = "I Love \uD83C\uDF7A!";
        int[] codePoints = str.codePoints().toArray();
        for (int v : codePoints) {
            System.out.print(v + " ");
        }
        System.out.println();
        System.out.println(new String(codePoints, 0, codePoints.length));
    }
}

3、构建字符串

我们之前说过,用+拼接 String 对象变量或执行substring等操作得到的字符串不共享。当需要频繁地拼接字符串时,每次执行 + 操作都会构建一个新的 String 对象,既耗时又浪费空间。

这时,我们可以使用StringBuffer或者StringBuilder类,来避免这个问题发生。

StringBuffer:线程安全、效率低;StringBuilder(Java5):线程不安全、效率高。

示例代码:

public class Main {
    public static void main(String[] args) {
        StringBuilder builder = new StringBuilder();
        for(int i = 0; i < 10; i++) {
            builder.append(i);
        }
        String str = builder.toString();
        System.out.println(str);
    }
}

如有错误,欢迎指正。.... .- ...- . .- -. .. -.-. . -.. .- -.-- -.-.--

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值