Java温故知新 字符串


一、常量池

对于源程序中出现的字符串常量,当程序运行时,会统一保存到一个常量池中进行缓存。
对引用这些缓存在常量池中的字符串的变量进行比较,用==也会得到正确的结果。

但在运行时,对字符串的各种操作如+、substring等等,都是会产生新的字符串对象的。
但是强大的编译器会对字符串常量的拼接进行优化,诸如s3 = "hell" + "o"时,s3仍然会
指向常量池中的字符串。但对于变量的运算,总不能要求虚拟机执行诸如s1 + s2时还要
判断结果是否已在常量池中了吧。因此,要用equals而非==去判断两个字符串是否相等。
	public static void main(String[] args) {

		// String constants are put in constant pool.
		String s1 = "hello";
		String s2 = "hello";
		String s3 = "hell" + "o";
		System.out.println(s1 == s2);
		System.out.println(s1 == s3);
		
		// Operation like +,substring on string create new one. 
		String s4 = "hell";
		String s5 = s4 + "o";
		System.out.println(s1 == s5);
		System.out.println(s1.equals(s5));
		
		// substring has special handle on substring(0)
		String s6 = s1.substring(0);
		System.out.println(s1 == s6);
	}

测试代码s1、s2、s3的字节码:

   0:   ldc     #16; //String hello
   2:   astore_1
   3:   ldc     #16; //String hello
   5:   astore_2
   6:   ldc     #16; //String hello
   8:   astore_3

测试代码s4、s5的字节码:
   
   41:  ldc     #30; //String hell
   43:  astore  4
   45:  new     #32; //class java/lang/StringBuilder
   48:  dup
   49:  aload   4
   51:  invokestatic    #34; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
   54:  invokespecial   #40; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   57:  ldc               #43; //String o
   59:  invokevirtual   #45; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   62:  invokevirtual   #49; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;


注意一点是substring方法,substring(0,3)是得到从字符0到2的字符串。这样设计的原因
也许是这样容易计算子串的长度,3-0=3。同时substring对于特殊参数有特别的优化处理:
    public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > count) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        if (beginIndex > endIndex) {
            throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
        }
        return ((beginIndex == 0) && (endIndex == count)) ? this :
            new String(offset + beginIndex, endIndex - beginIndex, value);
    }

由此看出,String对象背后并没有什么神奇之处,对字节码有了些了解可以更好的理解它。
其实常量池中还保存类及其方法的很多信息,如包名、类名、方法签名等等,有兴趣可以
深入研究。


二、性能

另外关于字符串的拼接,从上面的字节码还可以看出一点,就是强大的编译器已经帮我们
将+自动优化为使用StringBuilder进行拼接,而非每次都产生一个新的String对象。强大的
编译器啊!

字符串拼接的性能测试:http://coolshell.cn/articles/2235.html


原文地址:https://www.cnblogs.com/xiaomaohai/p/6157830.html