《字符串连接你用+还是用StringBuilder》续

作者:超人汪小建(seaboat)

出处:https://blog.csdn.net/wangyangzhizhou/column/info/16032


前言

前面的一篇文章《字符串连接你用+还是用StringBuilder》,有朋友找我反馈了一些问题,其中一位朋友说JDK10下生成的字节码跟文章中并不一样,这里继续看下是什么情况。

问题描述

如下图,按照《字符串连接你用+还是用StringBuilder》的代码在 javap 后发现它并没有创建 StringBuilder 类和一些相应的操作,与文章的描述的并不符合,使用的JDK版本为JDK10。

2019070531_1.png

2019070531_2.png

问题原因

JDK9及以后的编译器已经改成用动态指令执行字节码了,具体的调用实现在 java.lang.invoke.StringConcatFactory 类中,也就是说指令没有生成到class文件中保存起来,而是运行时生成。
具体实现如下,有六种策略,前五种还是用StringBuilder实现。
2019070531_3.png

JDK9前后

对于下面简单的例子,JDK8及之前和JDK9及之后编译的字节码有什么差别

    public class TestString2 {
        public static void main(String[] args) {
            String s = "www";
            for (int i = 0; i < 10; i++)
                s += i;
        }
    }

JDK8及之前,

    public class com.seaboat.string.TestString2 {
      public com.seaboat.string.TestString2();
        Code:
           0: aload_0
           1: invokespecial #8                  // Method java/lang/Object."<init>":()V
           4: return

      public static void main(java.lang.String[]);
        Code:
           0: ldc           #16                 // String www
           2: astore_1
           3: iconst_0
           4: istore_2
           5: goto          30
           8: new           #18                 // class java/lang/StringBuilder
          11: dup
          12: aload_1
          13: invokestatic  #20                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
          16: invokespecial #26                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
          19: iload_2
          20: invokevirtual #29                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
          23: invokevirtual #33                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
          26: astore_1
          27: iinc          2, 1
          30: iload_2
          31: bipush        10
          33: if_icmplt     8
          36: return
    }

JDK9及之后,

    public class com.seaboat.string.TestString2 {
      public com.seaboat.string.TestString2();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return

      public static void main(java.lang.String[]);
        Code:
           0: ldc           #2                  // String www
           2: astore_1
           3: iconst_0
           4: istore_2
           5: iload_2
           6: bipush        10
           8: if_icmpge     25
          11: aload_1
          12: iload_2
          13: invokedynamic #3,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
          18: astore_1
          19: iinc          2, 1
          22: goto          5
          25: return
    }

InvokeDynamic

可以看到JDK9之后生成的字节码是比较简洁的,只有一个 InvokeDynamic 指令,编译器会给该类字节码增加 invokedynamic 指令相关内容,包括方法句柄、引导方法、调用点、方法类型等等。它会调用 java.lang.invoke.StringConcatFactory 类中的makeConcatWithConstants方法,它有六种策略来处理字符串。如下代码所示,有默认的策略,也可以通过java.lang.invoke.stringConcat启动参数来修改策略。

    private static final Strategy DEFAULT_STRATEGY = Strategy.MH_INLINE_SIZED_EXACT;

    static {
            STRATEGY = DEFAULT_STRATEGY;

            Properties props = GetPropertyAction.privilegedGetProperties();
            final String strategy =
                    props.getProperty("java.lang.invoke.stringConcat");
            CACHE_ENABLE = Boolean.parseBoolean(
                    props.getProperty("java.lang.invoke.stringConcat.cache"));
            DEBUG = Boolean.parseBoolean(
                    props.getProperty("java.lang.invoke.stringConcat.debug"));
            final String dumpPath =
                    props.getProperty("java.lang.invoke.stringConcat.dumpClasses");

            STRATEGY = (strategy == null) ? DEFAULT_STRATEGY : Strategy.valueOf(strategy);
            CACHE = CACHE_ENABLE ? new ConcurrentHashMap<>() : null;
            DUMPER = (dumpPath == null) ? null : ProxyClassesDumper.getInstance(dumpPath);
        }
    private enum Strategy {
            BC_SB,
            BC_SB_SIZED,
            BC_SB_SIZED_EXACT,
            MH_SB_SIZED,
            MH_SB_SIZED_EXACT,
            MH_INLINE_SIZED_EXACT
        }

有六种策略,前五种还是用StringBuilder实现,而默认的策略MH_INLINE_SIZED_EXACT,这种策略下是直接使用字节数组来操作,并且字节数组长度预先计算好,可以减少字符串复制操作。实现的核心是通过 MethodHandle 来实现 runtime,具体实现逻辑在MethodHandleInlineCopyStrategy.generate方法中。

    private static MethodHandle generate(Lookup lookup, String className, MethodType mt, Recipe recipe) throws StringConcatException {
            try {
                switch (STRATEGY) {
                    case BC_SB:
                        return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.DEFAULT);
                    case BC_SB_SIZED:
                        return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED);
                    case BC_SB_SIZED_EXACT:
                        return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED_EXACT);
                    case MH_SB_SIZED:
                        return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED);
                    case MH_SB_SIZED_EXACT:
                        return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED_EXACT);
                    case MH_INLINE_SIZED_EXACT:
                        return MethodHandleInlineCopyStrategy.generate(mt, recipe);
                    default:
                        throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented");
                }
            } catch (Error | StringConcatException e) {
                throw e;
            } catch (Throwable t) {
                throw new StringConcatException("Generator failed", t);
            }
        }

————-推荐阅读————

赞(0) 打赏

如未加特殊说明,此网站文章均为原创,转载必须注明出处。Java 技术驿站 » 《字符串连接你用+还是用StringBuilder》续
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

关注【Java 技术驿站】公众号,每天早上 8:10 为你推送一篇技术文章

扫描二维码关注我!


关注【Java 技术驿站】公众号 回复 “VIP”,获取 VIP 地址永久关闭弹出窗口

免费获取资源

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏