Java SE API know how-字符串

Java SE API know how

字符串
紧凑字符串

java8
无论字符串的编码,最终都会编码为16位字符数组。

java11
字符串会编码为8位字节数组,这些字符串被称为紧凑字符串(compact string)
一个普通的java字符串的大小约是java8相同字符串的一半。
3-10倍的性能提升对比。
-XX:+CompactString 默认开启
except instance
应用程序中所有字符串都需要16位编码,和非紧凑字符串相比,在紧凑字符串中对这些字符串操作会稍长时间

重复字符串和字符串保留

创建很多包含相同字符序列的对象,基于对象不可变占用额外空间。
要知道是否有大量重复对象需要进行堆分析
jcmd <process id/main class> GC.class_histogram
jcmd <process id/main class> Thread.print

字符串去重

让jvm找到重复字符串后去重(deduplicate)
只有使用G1 GC + -XX:UseStringDeduplication标志 默认false
pros and cons

  1. 需要在G1 GC的新生代回收和混合回收阶段进行额外处理,延长回收时间
  2. 需要一个额外线程与应用线程同时运行,占用应用程序线程CPU周期
  3. 如果重复字符串很少,可能达不到效果

java工程师估计预期受益10%

#jdk8

-XX:PrintStringDeduplicationStatistics

#jdk11 after

-Xlog:gc+stringdebup*=debug

[0.896s][debug][gc,stringdedup] Last Exec: 110.434ms, Idle: 729.700ms,
Blocked: 0/0.000ms
[0.896s][debug][gc,stringdedup] Inspected: 62420
[0.896s][debug][gc,stringdedup] Skipped: 0( 0.0%)
[0.896s][debug][gc,stringdedup] Hashed: 62420(100.0%)
[0.896s][debug][gc,stringdedup] Known: 0( 0.0%)
[0.896s][debug][gc,stringdedup] New: 62420(100.0%)
3291.7K
[0.896s][debug][gc,stringdedup] Deduplicated: 15604( 25.0%)
731.4K( 22.2%)
[0.896s][debug][gc,stringdedup] Young: 0( 0.0%)
0.0B( 0.0%)
[0.896s][debug][gc,stringdedup] Old: 15604(100.0%)
731.4K(100.0%)

晋升的字符串可以进行去重的时间点是通过

-XX:StringDeduplicationAgeThreshold=N标志 默认3

字符串保留

编程层面处理字符串重复的方法是使用原生方法intern()方法

保留字符串存在特殊的哈希表中——哈希表存储在原生内存中,字符串存储在堆中

-XX:StringTableSize=N设置

【固定大小的哈希表】

哈希表包含一个数组,该数组容纳一定数量的条目,数组中每个元素被称为一个桶

当某个对象被存储在哈希表中是,存储索引是通过hashCode % numberOfBuckets计算

这个方案可能让两个具有不同hashCode值的对象很可能被映射到同一个桶

每个桶实际上是映射到该桶的所有存储项的链表

当两个对象被映射到同一个桶,这种情况被称为碰撞(collision)

当哈希表有更多对象之后,碰撞就会更多,更多的项存储到每个链表中

查找某个项需要遍历链表搜索,如果链表很长则增加搜索市场

通过调整哈希表可以有更多桶(减少碰撞),通过动态调整哈希表的大小的设计java中的HashMap便是如此。

【字符串保留和相等比较】

String.intern()更快

保留字符串可以通过==比较,是一个更快的引用比较

保留字符串的成本:计算字符串的hash码,也就是扫描整个字符串,并对每个字符进行hash操作

String.equals比较方式

不等长不相同

等长进行比较找到未必配字符

自定义字符串保留

Your mileage may very

字符串连接

jdk8 and jdk11 and optimal


@Benchmarkpublic void testSingleStringBuilder(Blackhole bh) {
    String s = new StringBuilder(prefix).append(strings[0]).toString();
    bh.consume(s);
}
@Benchmark
public void testSingleJDK11Style(Blackhole bh) {
    String s = prefix + strings[0];
    bh.consume(s);
}
@Benchmark
public void testSingleJDK8Style(Blackhole bh) {
    String s = new StringBuilder().append(prefix).append(strings[0]).toString();
    bh.consume(s);
}

在这里插入图片描述

  1. 手动操作进行编码
  2. jdk11编译进行最新的编译器优化
  3. jdk8和11都是一样的编译方式

最终数据显示发现两种优化都比手动编码好,只是编译器没有发现特定的模式。

如果jdk8对字符串和其他类型进行链接则情况

@Benchmark
public void testDoubleJDK11Style(Blackhole bh) {
    double d = 1.0;
    String s = prefix + strings[0] + d;
    bh.consume(s);
}
@Benchmark
public void testDoubleJDK8Style(Blackhole bh) {
    double d = 1.0;
    String s = new StringBuilder().append(prefix).
                   append(strings[0]).append(d).toString();
    bh.consume(s);
}

在这里插入图片描述

jdk8在处理非字符串链接类型时候对整形处理的较好,但是双精度情况下则会跳过优化,复原了手动编码的性能。

多次链接操作或者循环中执行链接操作的测试数据


@Benchmark
public void testJDK11Style(Blackhole bh) {
    String s = "";
    for (int i = 0; i < nStrings; i++) {
        s = s + strings[i];
    }
    bh.consume(s);
}
@Benchmark
public void testJDK8Style(Blackhole bh) {
    String s = "";
    for (int i = 0; i < nStrings; i++) {
        s = new StringBuilder().append(s).append(strings[i]).toString();
    }
    bh.consume(s);
}
@Benchmark
public void testStringBuilder(Blackhole bh) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < nStrings; i++) {
        sb.append(strings[i]);
    }
    bh.consume(sb.toString());
}

在这里插入图片描述

测试结果显示最好的性能是手动编码方式

当在多次拼接或者循环中链接字符串最好显式使用StringBuilder对象获取更好的性能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

P("Struggler") ?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值