代码举例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 theequals(Object)
method, then the string from the pool is returned. Otherwise, thisString
object is added to the pool and a reference to thisString
object is returned.It follows that for any two strings
s
andt
,s.intern() == t.intern()
istrue
if and only ifs.equals(t)
istrue
.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)