1、常量池
Java 被编译成 class 文件时,会生成一个常量池(Constant pool)的数据结构,用于保存字面常量和符号引用(类名、方法名、接口名和字段名等)。常量池的内存在 Java 堆上进行分配,意味着常量池不受固定大小的限制。
2、字符串初始化
字符串初始化的方式有两种:字面常量、String 对象
(1)如下一段代码:
String a = "java";
String b = "java";
String c = "ja" + "va";
System.out.println("一致性"+(a==b));
System.out.println("一致性"+(a==c));
System.out.println("一致性"+(b==c));
输出结果是:
![](https://img.haomeiwen.com/i7512354/74033785a7e6c444.jpg)
我们通过 javap -c 命令分析一下字节码指令的实现:
![](https://img.haomeiwen.com/i7512354/3bdc345f14062c31.jpg)
通过上图可以看出,ldc 指令将 int、float、String 等类型的常量值从常量池中推送到栈顶,所以 a、b 都指向常量池中 "java" 字符串,同时也可以发现 c 也指向常量池中 "java" 字符串,那是因为在编译期间,表达式 "ja"+"va" 已经将结果值 "java" 直接赋值给 c
(2)如下一段代码:
String a = "java";
String c = new String("java");
System.out.println((a==c));
输出结果为:
![](https://img.haomeiwen.com/i7512354/b64cb1ae15bdc9fc.jpg)
我们通过 javap -c 命令分析一下字节码指令的实现:
![](https://img.haomeiwen.com/i7512354/e673ab4ea2b5a81b.jpg)
通过上图可以分析出,a 指向常量池中的 "java" 字符串,c 指向 Java 堆中新建的 String 对象,而该对象的 char 数组则指向常量池中的 "java" 字符串,所以得出结论,a != c
(3)如下一段代码:
String a = "ja";
String b = "va";
String c = a + b;
String d = "java";
System.out.println(c == d);
输出结果:
![](https://img.haomeiwen.com/i7512354/f227c760a2746440.jpg)
我们通过 javap -c 命令分析一下字节码指令的实现:
![](https://img.haomeiwen.com/i7512354/5060be7a1cbd45e4.jpg)
可以看到会创建一个 StringBuilder 对象,然后将 a、b 拼接起来,然后通过 toString 方法产生一个 String 对象 c,而 d 是直接指向常量池中的字符串 "java",所以得出 c != d
(4)但如果是如下代码:
final String a = "ja";
final String b = "va";
String c = a + b;
String d = "java";
System.out.println(c == d);
则能输出 true
我们通过 javap -c 命令分析一下字节码指令的实现:
![](https://img.haomeiwen.com/i7512354/513f6ca8c2870338.jpg)
通过上图可以看出,用 final 修饰后,在编译期间,就已经将 a 和 b 拼接好赋值给 c 了,然后 c 和 d 同时指向常量池中字符串 "java",所以得出的结论是 c == d
3、String、StringBuffer和StringBuilder的区别
三者本质上都是字符数组
(1)String 是不可变的对象,因此每次改变 String 的时候,都会产生一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串不要使用 String,如果要操作少量的数据,则可以使用 String
(2)单线程操作大量数据,使用 StringBuilder,因为不用保证线程同步,所以速度更快
(3)多线程操作大量数据,使用 StringBuffer,因为需要考虑线程同步,所以速度更慢一些
网友评论