您的位置 首页 java

Java 深入JVM分析String StringTable

文章目录

基本特性:

1、 字符串常量 Jdk 1.7之前位于方法区,1.7开始位于堆

2、字符串常量池中同样的数据只存储一份(固定大小 HashTable 存储数据)

3、使用 -XX: String TableSize 可设置大小,不会像 HashMap 一样动态扩容,值太小造型 Hash 冲突严重,调用String.interns时性能会大幅下降

4、Jdk1.8中默认大小60013,1009是可设置最小值

字符串 拼接:

1、通过 StringBuilder append ()方法拼接字符串,自始至终只会创建一个StringBuilder的对象

2、使用String的字符串拼接,每次拼接都会创建一个StringBuilder和String对象,内存占用增大,也会增加GC频率

字符串拼接优化:

1、理论上初始化StringBuilder对象时,指定大小,从而设的数组大小,可以提高效率(减少扩容、复制次数)

2、但是,通过测试,发现设定大小与不设定,耗时相差无几(毫秒)

intern方法的使用:

情况一:intern方法会从字符串常量池中查询当前字符串是否存在,若不存在,则会将当前字符串放入常量池中并把地址返回栈中引用,存在则将地址返回给栈中引用。

 String s1 = "JavaEEHadoop"; //在字符串常量池中创建 "JavaEEHadoop"
String s2 = new String("JavaEEHadoop").intern(); //会将字符串常量池中 "JavaEEHadoop" 地址 返回s2
System.out.println(s1 == s2); true  

情况二:如果对象在堆中已经创建了,字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用,也就节省了一部分的内存空间

下述情况适用于Jdk1.7、1.8

 /**
* 此代码会在字符串常量池中 创建 "JavaEE"、” Hadoop “
* 会使用StringBUilder来拼接,最后执行 toString 方法
* 
* 此时在堆中是存在值为 "JavaEEHadoop" 的字符串对象的
*/String s1 = new String("JavaEE") + new String("Hadoop");

/**
* 由于s1在堆中已存在,因此为了节省空间,字符串常量池中并不会创建 "JavaEEHadoop"
* 而是保存 堆中对象的引用
*/s1.intern();

/**
* 由于此刻字符串常量池中已存在,因此s2会指向常量池中 堆中对象的引用
*/String s2 = "JavaEEHadoop";

System.out.println(s1 == s2); true  

是否引用同一份对象?

 String s1 = "JavaEE";
String s2 = "Hadoop";

String s3 = "JavaEEHadoop";
/**
* 编译器优化 为 JavaEEHadoop,s3 == s4 为true,在字符串常量池中同一份
*/String s4 = "JavaEE" + "Hadoop";
System.out.println(s3 == s4); true

/**
* 如果拼接符号的前后出现了变量
* 1、StringBuilder.toString中 s = new StringBuilder();
* 2、s.append("JavaEE");
* 3、s.append("Hadoop");
* 4、s.toString(); -->类似于 new String(char[])
* 但跟String s = new String("JavaEEHadoop")不一样
* 由于StringBuilder.toString中new String中参数是char[]数组
* 因此并不会在字符串常量池中创建 ”JavaEEHadoop“
*/String s1 = "JavaEE"; //字符串常量池中 "JavaEE"
String s2 = "Hadoop"; //字符串常量池中 "Hadoop"
String s3 = "JavaEEHadoop";
String s4 = s1 + "Hadoop"; 
String s5 = s1 + s2;
System.out.println(s3 == s4); false
System.out.println(s3 == s5); false

/**
* final修饰的string变量相加时,编译器会优化为 ab,不会用StringBuilder拼接
* s11 == s12 为true,在字符串常量池中同一份
* s12会被编译器优化为 "ab"
*/final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
System.out.println(s2 == s4); true  

创建了几个对象?

 /**
* 创建了1个或2个
* 执行步骤,对应 字节码 步骤
* 
* 1、堆中开辟String对象空间  new #8 <java/lang/StringBuilder> 
* 2、如果 "ab" 在字符串常量池中存在,那么久不创建,如果不存在则创建
* 3、初始化String对象
*/String s1 = new String("ab");
/**
* 字节码
*/步骤1、new #17 <java/lang/String>
步骤1、dup
步骤2、ldc #14 <ab>
步骤3、invokespecial #18 <java/lang/String.<init>>


/**
* 如果字符串常量池中 "a"、"b"不存在,那么会创建6个对象
* 
* 执行步骤对应字节码步骤
* 1、堆中开辟StringBuilder对象空间,初始化StringBuilder对象  
* 2、堆中开辟String对象空间
* 3、在字符串常量池中创建 "a"
* 4、初始化String对象
* 5、执行append方法
* 
* 6、堆中开辟String对象空间
* 7、在字符串常量池中创建 "b"
* 8、初始化String对象
* 9、执行append方法
* 10、执行toString方法

* StringBuilder对象toString方法执行说明,由于返回的是String对象,因此会执行toString方法
* 11、堆中开辟String对象空间
* 由于调用的是new String(char[]) 构造方法 
* 因此并不会在字符串常量池中创建 "ab"
*/String s1 = new String("a") + new String("b");

/**
* 字节码文件
*/步骤1、new #8 <java/lang/StringBuilder>
步骤1、dup
步骤1、invokespecial #9 <java/lang/StringBuilder.<init>>
步骤2、new #17 <java/lang/String>
步骤2、dup
步骤3、ldc #12 <a>
步骤4、invokespecial #18 <java/lang/String.<init>>
步骤5、invokevirtual #10 <java/lang/StringBuilder.append>
    
步骤6、new #17 <java/lang/String>
步骤6、dup
步骤7、ldc #13 <b>
步骤8、invokespecial #18 <java/lang/String.<init>>
步骤9、invokevirtual #10 <java/lang/StringBuilder.append>
步骤10、invokevirtual #11 <java/lang/StringBuilder.toString>
    
步骤11、new #80 <java/lang/String>
步骤11、dup
//我们可以看到toString方法中并没有 在字符串常量池中创建 "ab"
aload_0
getfield #234 <java/lang/StringBuilder.value>
iconst_0
aload_0
getfield #233 <java/lang/StringBuilder.count>
invokespecial #291 <java/lang/String.<init>>
areturn  

提示:这里对文章进行总结:

建议大家不要卷、不要卷、不要卷

文章来源:智云一二三科技

文章标题:Java 深入JVM分析String StringTable

文章地址:https://www.zhihuclub.com/189862.shtml

关于作者: 智云科技

热门文章

网站地图