查看程序中的StringPool的 字符串个数
public static void main(String[] args) {
System.out.println();
System.out.println("1");
System.out.println("2");
System.out.println("3");
System.out.println("4");
System.out.println("5");
System.out.println();
System.out.println("1");
System.out.println("2");
System.out.println("3");
System.out.println("4");
System.out.println("5");
}
可以看到我当前的字符串个数为 2474 个。 当我依次打印到 第一个 “5” 的时候
常量池中的个数增加到了 2480 个。 当我打印到 第二个 "5"的时候。,
还是 2480 个,也就是说字面量的String 实例。只能拥有一份。
Java 语言规范里要求完全相同的字符串字面量,应该包含同样的 Unicode 字符序列(包含同一份码点序列的常量), 并且必须是指向同一个String类实例。
intern()
方法, 则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。第一个示例
/**
* 常量与常量的拼接,结果在常量池,原理是 编译器优化。
*/
public static void test1() {
String str1 = "a" + "b" + "c";
String str2 = "abc";
System.out.println(str1 == str2); // true
System.out.println(str1.equals( str2)); // true
}
通过 == 判断字符串 为 true 说明两者之间的地址是同一个
第二个示例:
/**
* 常量池中不会存在相同内容的常量
*/
public static void test2() {
/**
* 常量池中不会存在相同内容的常量
*/
public static void test2() {
String str1 = "javaEE";
String str2 = "hadoop";
String str3 = "javaEEhadoop";
String str4 = "javaEE" + "hadoop";
String str5 = str1 + "hadoop";
String str6 = "javaEE" + str2;
String str7 = str1 + str2;
System.out.println(str3 == str4); // true
System.out.println(str3 == str5); // false
System.out.println(str3 == str6); // false
System.out.println(str3 == str7); // false
System.out.println(str5 == str6); // false
System.out.println(str5 == str7); // false
System.out.println(str6 == str7); // false
// intern() 判断字符串常量池中是否存在javaEEhadoop 值,如果存在,则返回常量池中的javaEEhadoop 的地址,
// 如果字符串常量池中不存在, 则在常量池中加载一份 javaEEhadoop, 并返回此对象的地址
String str8 = str6.intern(); // 如果拼接的结果调用的`intern()` 方法, 则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。
System.out.println(str3 == str8); // true
}
其实这个面试题也挺常见的。
str1 + str2
其实具体的操作就是
StringBuilder s = new StringBuilder();
s.append(str1);
s.append(str2);
s.toString(); // 内部其实就是new String("javaEEhadoop")
字符串拼接也不一定全是不相等的
/**
* 字符串拼接不一定全是使用的StringBuilder()
* 如果拼接符号左右两边都是字符串常量或者常量引用,则依然使用编译器优化,即非StringBuilder 的方式、
* 针对于 final 修饰类,方法,基本数据类型,引用数据类型的量的结构时,能使用final 尽量使用。
*/
public static void test3() {
final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
System.out.println(s3 == s4); /// true
}
感受一下 StringBuilder 和 字符串 +
号方式拼接的效率
public static void main(String[] args) {
long start = System.currentTimeMillis();
// test4(100000); // 字符串拼接的方式:25175
test5(100000); // StringBuilder 的方式: 13 毫秒
long end =System.currentTimeMillis();
System.out.println(end - start);
}
public static void test4(int count) {
String src = "";
for (int i = 0; i < count; i ++) {
src = src + i;
}
}
public static void test5(int count) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
sb.append(i);
}
System.out.println(sb.toString());
}
我这边执行, 通过+
拼接的方式耗时 25175 ms
通过 StringBuilder 的append() 方式只有 13 ms
效率执行: 通过StringBuilder 的append() 方式添加字符串的效率远高于使用String的字符串的拼接方式!
详情:①. StringBuilder 的 append() 方式,自始至终,都只是一个 StringBuilder 的对象,而 String 的拼接方式,每次拼接都需要创建一次 StringBuilder 对象
②. 内存中由于创建了比较多的StringBuilder和String 对象,内存占用更大,如果需要进行GC,则需要花费更多的时间。
③. 在实际开发中,如果基本确定前前后后添加的字符串长度不会高于某个限定的值的情况下,建议使用构造器创建StringBuilder 对象, StringBuilder s = new StringBuilder(highLevel);
如果不是双引号声明的String对象,可以使用String提供的intern()
方法,intern()
会从字符串常量池中查询当前字符串是否存在,如果存在就将当前字符串放入常量池中
String.intern()
方法,那么其返回的结果所指向的那个实例,必须和直接以常量形式出现的字符串示例完全相同,因此,下列表达式的值必定为 true.("a" + "b" + "c").inter() == "abc"
Interned String
就是确保字符串在内存中只有一份拷贝,这样可以节省内存空间,加快字符串操作任务的执行速度,注意这个值会被存放在字符串内部池 (String Intern Pool)public static void main(String[] args) {
String str = new String("ab");
}
字节码文件对应得部分
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String ab
6: invokespecial #4 // Method java/lang/String."":(Ljava/lang/String;)V
9: astore_1
10: return
LineNumberTable:
line 34: 0
line 36: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
10 1 1 str Ljava/lang/String;
MethodParameters:
Name Flags
args
可以看到 当前 new 出来的对象实在堆空间创建得,另一个对象是字符串穿常量池中得对象。 字节码指令: ldc
public static void main(String[] args) {
String s1 = new String("1") + new String("2");
}
class 字节码文件对应部分
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."":()V
7: new #4 // class java/lang/String
10: dup
11: ldc #5 // String 1
13: invokespecial #6 // Method java/lang/String."":(Ljava/lang/String;)V
16: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: new #4 // class java/lang/String
22: dup
23: ldc #8 // String 2
25: invokespecial #6 // Method java/lang/String."":(Ljava/lang/String;)V
28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: astore_1
35: return
LineNumberTable:
line 36: 0
line 37: 35
LocalVariableTable:
Start Length Slot Name Signature
0 36 0 args [Ljava/lang/String;
35 1 1 s1 Ljava/lang/String;
MethodParameters:
Name Flags
args
从字节码指令可以看出来,实际上是以下几个对象:
对象 1 : StringBuilder 对象
对象 2 : String 对象 new String("a")
对象 3 : 常量池中的 a
对象 4 : Striing 对象 new String("b")
对象 5 : 常量池中的 b
最后调用了 StringBuilder 的 toString()
方法, 内部返回了一个 new String(value, 0, count);
public static void main(String[] args) {
String s = new String("1"); // 创建 String 对象的同时,在字符串常量池中也添加了一个 "1" 的常量。
s.intern(); // 调用 intern 并不会创建一个新的常量对象。
String s2 = "1"; // 所以 字面量赋值 "1" 的方式是在常量池中的 "1" 而此时的 s 指向的是 堆中的 "1"
System.out.println(s == s2); // jdk6: false jdk7/8: false // 所以此处的 == 判断比较地址 所以为 false
String s3 = new String("1") + new String("1"); // 当使用 new String("1") + new String("1") 的时候,并没有在 常量池中创建 "11" 的常量。
s3.intern(); // 所以当使用 intern() 方法的时候。 jdk6: 是会在常量池中创建一个新的对象 "11" 也就是一个新的地址。 但是 jdk7 的时候, 此时在常量池中并没有创建 "11" , 而是创建了一个指向堆空间的一个引用。所以 此时的常量池的引用地址还是存在于 对空间中。
String s4 = "11";// 当时用 jdk6 的时候, 使用 s4 = "11" 的赋值引用指向的常量池中的全新的 "11" 对象,而使用jdk7的时候,s4 ="11" 的指向的是存在于常量池中的一个指向堆空间 "11"的地址。 所以
System.out.println(s3 == s4); // jdk6: false jdk7/8: true 在使用== 的时候, jdk6 和 jkdk7 会有不同的结果。
}