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
|
1月前
|
Java Docker 索引
记录一次索引未建立、继而引发一系列的问题、包含索引创建失败、虚拟机中JVM虚拟机内存满的情况
这篇文章记录了作者在分布式微服务项目中遇到的一系列问题,起因是商品服务检索接口测试失败,原因是Elasticsearch索引未找到。文章详细描述了解决过程中遇到的几个关键问题:分词器的安装、Elasticsearch内存溢出的处理,以及最终成功创建`gulimall_product`索引的步骤。作者还分享了使用Postman测试接口的经历,并强调了问题解决过程中遇到的挑战和所花费的时间。
|
1月前
|
存储 算法 Oracle
不好意思!耽误你的十分钟,JVM内存布局还给你
先赞后看,南哥助你Java进阶一大半在2006年加州旧金山的JavaOne大会上,一个由顶级Java开发者组成的周年性研讨会,公司突然宣布将开放Java的源代码。于是,下一年顶级项目OpenJDK诞生。Java生态发展被打开了新的大门,Java 7的G1垃圾回收器、Java 8的Lambda表达式和流API…大家好,我是南哥。一个Java学习与进阶的领路人,相信对你通关面试、拿下Offer进入心心念念的公司有所帮助。
不好意思!耽误你的十分钟,JVM内存布局还给你
|
1月前
|
存储 算法 Java
JVM自动内存管理之垃圾收集算法
文章概述了JVM内存管理和垃圾收集的基本概念,提供一个关于JVM内存管理和垃圾收集的基础理解框架。
JVM自动内存管理之垃圾收集算法
|
1月前
|
存储 Java 程序员
JVM自动内存管理之运行时内存区
这篇文章详细解释了JVM运行时数据区的各个组成部分及其作用,有助于理解Java程序运行时的内存布局和管理机制。
JVM自动内存管理之运行时内存区
|
1月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
1月前
|
存储 安全 Java
JVM内存结构
这篇文章详细介绍了Java虚拟机(JVM)的内存结构,包括类的加载过程、类加载器的双亲委派机制、沙箱安全机制、程序计数器、Java栈、Java堆、本地方法和本地方法栈等关键组件及其作用。
JVM内存结构
|
2月前
|
运维 Java Linux
(九)JVM成神路之性能调优、GC调试、各内存区、Linux参数大全及实用小技巧
本章节主要用于补齐之前GC篇章以及JVM运行时数据区的一些JVM参数,更多的作用也可以看作是JVM的参数列表大全。对于开发者而言,能够控制JVM的部分也就只有启动参数了,同时,对于JVM的性能调优而言,JVM的参数也是基础。