jvm之StringTable解读(二)

简介: jvm之StringTable解读(二)

代码举例4

1. public void test3(){
2. String s1 = "a";
3. String s2 = "b";
4. String s3 = "ab";
5. String s4 = s1 + s2;
6.     System.out.println(s3==s4);
7. }

我们拿例4的字节码进行查看,可以发现s1 + s2实际上是new了一个StringBuilder对象,并使用了append方法将s1和s2添加进来,最后调用了toString方法赋给s4

1. 0 ldc #2 <a>
2. 2 astore_1
3. 3 ldc #3 <b>
4. 5 astore_2
5. 6 ldc #4 <ab>
6. 8 astore_3
7. 9 new #5 <java/lang/StringBuilder>
8. 12 dup
9. 13 invokespecial #6 <java/lang/StringBuilder.<init>>
10. 16 aload_1
11. 17 invokevirtual #7 <java/lang/StringBuilder.append>
12. 20 aload_2
13. 21 invokevirtual #7 <java/lang/StringBuilder.append>
14. 24 invokevirtual #8 <java/lang/StringBuilder.toString>
15. 27 astore 4
16. 29 getstatic #9 <java/lang/System.out>
17. 32 aload_3
18. 33 aload 4
19. 35 if_acmpne 42 (+7)
20. 38 iconst_1
21. 39 goto 43 (+4)
22. 42 iconst_0
23. 43 invokevirtual #10 <java/io/PrintStream.println>
24. 46 return

1. 将字符串"a"压入操作数栈。
2. 通过字节码指令"astore_1"将字节码操作数栈顶的引用类型值(String类型)存储到局部变量表的第1个槽位(s1)。
3. 将字符串"b"压入操作数栈。
4. 通过字节码指令"astore_2"将字节码操作数栈顶的引用类型值(String类型)存储到局部变量表的第2个槽位(s2)。
5. 将字符串"ab"压入操作数栈。
6. 通过字节码指令"astore_3"将字节码操作数栈顶的引用类型值(String类型)存储到局部变量表的第3个槽位(s3)。
7. 通过字节码指令"new"创建StringBuilder对象。
8. 通过字节码指令"dup"复制该StringBuilder对象的引用,并将复制后的引用压入操作数栈顶。
9. 通过字节码指令"invokespecial"调用StringBuilder的构造器,并将s1压入操作数栈作为构造器参数。
10. 通过字节码指令"aload_2"将局部变量表中s2的值(引用类型)压入操作数栈,并在StringBuilder对象上调用append方法。
11. 通过字节码指令"invokevirtual"调用StringBuilder的toString方法,并将返回值("ab")压入操作数栈。
12. 将局部变量表中s4的值(引用类型)压入操作数栈。
13. 通过字节码指令"if_acmpne"比较操作数栈顶的两个引用类型值(s3和s4),如果不相等则跳转到16行。
14. 通过字节码指令"getstatic"获取System.out对象的引用,并将操作数栈顶的"true"值(int类型)强制转换为Object类型,然后调用println方法输出到控制台。
15. 通过字节码指令"goto"跳转到17行。
16. 通过字节码指令"getstatic"获取System.out对象的引用,并将操作数栈顶的"false"值(int类型)强制转换为Object类型,然后调用println方法输出到控制台。
17. 通过字节码指令"return"结束方法的执行。

字符串拼接操作性能对比

1. public class Test
2. {
3. public static void main(String[] args) {
4. int times = 50000;
5. 
6. // String
7. long start = System.currentTimeMillis();
8.         testString(times);
9. long end = System.currentTimeMillis();
10.         System.out.println("String: " + (end-start) + "ms");
11. 
12. // StringBuilder
13.         start = System.currentTimeMillis();
14.         testStringBuilder(times);
15.         end = System.currentTimeMillis();
16.         System.out.println("StringBuilder: " + (end-start) + "ms");
17. 
18. // StringBuffer
19.         start = System.currentTimeMillis();
20.         testStringBuffer(times);
21.         end = System.currentTimeMillis();
22.         System.out.println("StringBuffer: " + (end-start) + "ms");
23.     }
24. 
25. public static void testString(int times) {
26. String str = "";
27. for (int i = 0; i < times; i++) {
28.             str += "test";
29.         }
30.     }
31. 
32. public static void testStringBuilder(int times) {
33. StringBuilder sb = new StringBuilder();
34. for (int i = 0; i < times; i++) {
35.             sb.append("test");
36.         }
37.     }
38. 
39. public static void testStringBuffer(int times) {
40. StringBuffer sb = new StringBuffer();
41. for (int i = 0; i < times; i++) {
42.             sb.append("test");
43.         }
44.     }
45. }
46. 
47. // 结果
48. String: 7963ms
49. StringBuilder: 1ms
50. StringBuffer: 4ms

本实验进行5万次循环,String拼接方式的时间是StringBuilder.append方式的约8000倍,StringBuffer.append()方式的时间是StringBuilder.append()方式的约4倍

可以看到,通过StringBuilder的append方式的速度,要比直接对String使用“+”拼接的方式快的不是一点半点

那么,在实际开发中,对于需要多次或大量拼接的操作,在不考虑线程安全问题时,我们就应该尽可能使用StringBuilder进行append操作

除此之外,还有那些操作能够帮助我们提高字符串方面的运行效率呢?

StringBuilder空参构造器的初始化大小为16。那么,如果提前知道需要拼接String的个数,就应该直接使用带参构造器指定capacity,以减少扩容的次数

1. /**
2.  * Constructs a string builder with no characters in it and an
3.  * initial capacity of 16 characters.
4.  */
5. public StringBuilder() {
6. super(16);
7. }
8. 
9. /**
10.  * Constructs a string builder with no characters in it and an
11.  * initial capacity specified by the {@code capacity} argument.
12.  *
13.  * @param      capacity  the initial capacity.
14.  * @throws     NegativeArraySizeException  if the {@code capacity}
15.  *               argument is less than {@code 0}.
16.  */
17. public StringBuilder(int capacity) {
18. super(capacity);
19. }

intern()的使用

官方API文档中的解释

public String intern()

Returns a canonical representation for the string object.

A pool of strings, initially empty, is maintained privately by the class String.

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10.5 of the The Java™ Language Specification.

  • Returns:
    a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.

当调用intern方法时,如果池子里已经包含了一个与这个String对象相等的字符串,正如equals(Object)方法所确定的,那么池子里的字符串会被返回。否则,这个String对象被添加到池中,并返回这个String对象的引用。

由此可见,对于任何两个字符串s和t,当且仅当s.equals(t)为真时,s.intern() == t.intern()为真。

所有字面字符串和以字符串为值的常量表达式都是interned。

返回一个与此字符串内容相同的字符串,但保证是来自一个唯一的字符串池。

intern是一个native方法,调用的是底层C的方法

public native String intern();

如果不是用双引号声明的String对象,可以使用String提供的intern方法,它会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。

String myInfo = new string("I love atguigu").intern();

也就是说,如果在任意字符串上调用String.intern方法,那么其返回结果所指向的那个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定是true

("a"+"b"+"c").intern() == "abc"

通俗点讲,Interned string就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池(String Intern Pool)

相关文章
|
Java C++
jvm之StringTable解读(三)
jvm之StringTable解读(三)
|
存储 缓存 Oracle
|
存储 Java C++
JVM系列之:String.intern和stringTable
JVM系列之:String.intern和stringTable
JVM系列之:String.intern和stringTable
|
5月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
472 55
|
6月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
446 6
|
9月前
|
存储 设计模式 监控
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
923 166
|
11月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
1796 1
|
7月前
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
322 29
JVM简介—1.Java内存区域
|
7月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
7月前
|
存储 设计模式 监控
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
168 0
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?