《高性能Java-缓存、序列化、String》

高性能Java-目录

高性能Java是一个系列文章,旨在总结编码过程中容易踩到的性能的坑以及一些规避这些坑的方法、工具。
该系列文章计划写以下内容,文章的链接也会在文章完成后添加到本文中:

  1. String
  2. 集合
  3. 序列化
  4. 缓存
  5. 压缩
  6. 日志
  7. IO
  8. 并发
  9. 异步

在编写高性能代码方面如果你有什么心得,欢迎与我沟通,共同学习,共同进步。

 

高性能Java-缓存

前言

字符串处理是程序逻辑中比重比较大的部分,由此带来的资源消耗也是比较多的。在编写代码进行字符串处理时如果能使用一些高效的方法或工具,起码能够帮我们规避一些性能上的坑,避免日后补救。

缓存算法

LRU

LFU

本地缓存

集中式缓存

缓存工具

Guava

Caffeine

EHCache

OSCache

高性能Java-序列化

前言

JSON

ProtoBuf

ProtoStuff

hessian

高性能Java-String篇

前言

字符串处理是程序逻辑中比重比较大的部分,由此带来的资源消耗也是比较多的。在编写代码进行字符串处理时如果能使用一些高效的方法或工具,起码能够帮我们规避一些性能上的坑,避免日后补救。

1.字符串拼接

  1. 不要用+,虽然在JDK7U40之后编译器会将”+”优化成StringBuilder的方式,但是StringBuilder初始化的时候是不会指定其初始容量的;
  2. 用StringBuilder:切记要指定其初始容量,避免扩容造成的CPU和内存浪费,这里造成的浪费还是很可观的。具体内容详见:StringBuilder你应该知道的几件事情
  3. 用Guava Joiner:对于用相同符号间隔的字符串拼接,可以使用Guava的Joiner,用起来很方便。但需要注意的是Joiner.on每次调用都会创建一个新的Joiner实例,会造成内存浪费。同时Joiner是线程安全的,所以对于相同分隔符创建的Joiner实例,公用一个单例就可以啦。
    1
    2
    3
    4
    5
    6
    /**
    * Returns a joiner which automatically places {@code separator} between consecutive elements.
    */
    public static Joiner on(char separator) {
    return new Joiner(String.valueOf(separator));
    }

2.字符串拆分

如果不需要用正则表达式,用StringUtils.split代替String.split,因为原生的split方法支持正则表达式,会导致性能偏低。

3.字符串替换

如果不需要用正则表达式,用StringUtils.replace代替String.replace,因为原生的replace方法支持正则表达式,会导致性能偏低。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Replaces each substring of this string that matches the literal target
* sequence with the specified literal replacement sequence. The
* replacement proceeds from the beginning of the string to the end, for
* example, replacing "aa" with "b" in the string "aaa" will result in
* "ba" rather than "ab".
*
* @param target The sequence of char values to be replaced
* @param replacement The replacement sequence of char values
* @return The resulting string
* @since 1.5
*/
public String replace(CharSequence target, CharSequence replacement) {
return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}

4.字符串转换

避免用String.format。如果你只是想要把一堆不同类型的参数转换成字符串,从性能的角度,建议你直接用StringBuilder实现。因为String.format其实也是用StringBuilder实现的,但由于它要解析format参数中的各种格式进行转换,导致性能降低。有人做过对比,String.format要比直接使用StringBuilder要慢5-30倍……话不多少,直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public Formatter format(String format, Object ... args) {
return format(l, format, args);
}

public Formatter format(Locale l, String format, Object ... args) {
ensureOpen();

// index of last argument referenced
int last = -1;
// last ordinary index
int lasto = -1;

FormatString[] fsa = parse(format);
for (int i = 0; i < fsa.length; i++) {
FormatString fs = fsa[i];
int index = fs.index();
try {
switch (index) {
case -2: // fixed string, "%n", or "%%"
fs.print(null, l);
break;
case -1: // relative index
if (last < 0 || (args != null && last > args.length - 1))
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[last]), l);
break;
case 0: // ordinary index
lasto++;
last = lasto;
if (args != null && lasto > args.length - 1)
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[lasto]), l);
break;
default: // explicit index
last = index - 1;
if (args != null && last > args.length - 1)
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[last]), l);
break;
}
} catch (IOException x) {
lastException = x;
}
}
return this;
}

上述代码用到了FormatString.print方法,那我们再来看看FormatString的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private interface FormatString {
int index();
void print(Object arg, Locale l) throws IOException;
String toString();
}

private class FixedString implements FormatString {
private String s;
FixedString(String s) { this.s = s; }
public int index() { return -2; }
public void print(Object arg, Locale l)
throws IOException { a.append(s); }
public String toString() { return s; }
}

FormatString是Formater的内部接口类,而FixedString实现了FormatString接口,FixedString.print方法用到了a.append()方法,看到append方法,你有没有似曾相识的赶脚呢?我们再来看看这个a是个什么鬼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public final class Formatter implements Closeable, Flushable {
private Appendable a;
private final Locale l;

private IOException lastException;

private final char zero;
private static double scaleUp;

// 1 (sign) + 19 (max # sig digits) + 1 ('.') + 1 ('e') + 1 (sign)
// + 3 (max # exp digits) + 4 (error) = 30
private static final int MAX_FD_CHARS = 30;

//此处省略一些代码

private static final Appendable nonNullAppendable(Appendable a) {
if (a == null)
return new StringBuilder();

return a;
}
////此处省略一万字
}

看到了吧,它还是用的StringBuilder,而且没有指定初始化容量,这样如果字符串比较长,扩容带来的资源消耗也是蛮高的。

5.toString()方法

toString方法一般是打印日志的时候使用。在这里提一下toString方法的原因是实现toString方法的方式有很多,有手写的、有用ide生成的、有用lombok生成的。这里只提2点:

  1. 不建议使用lombok的ToString注解,因为lombok生成的toString方法是用”+”做字符串拼接的,如果打印日志频繁,这里的不必要的性能开销会比较大;
    下面的代码是使用了lombok Data和ToString注解的源代码,我们来看看经过lombok处理之后的代码是什么样子。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package io.fengfu.learning.lombok;

    import lombok.Data;
    import lombok.ToString;

    import java.util.Date;

    @ToString
    @Data
    public class Wrapper {
    private String wrapperId;
    private boolean isOneWay;
    private String state;
    private int stateCode;
    private String operator;
    private Date date;
    private String operateTime;
    private String detail;
    }

下面是lombok生成的代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Wrapper {
private String wrapperId;
private boolean isOneWay;
private String state;
private int stateCode;
private String operator;
private Date date;
private String operateTime;
private String detail;

public String toString() {
return "Wrapper(wrapperId=" + getWrapperId() +
", isOneWay=" + isOneWay() +
", state=" + getState() +
", stateCode=" + getStateCode() +
", operator=" + getOperator() +
", date=" + getDate() +
", operateTime=" +
getOperateTime() +
", detail=" +
getDetail() + ")";
}
}

看到了吗?是用”+”拼接的……

  1. 输出格式要统一,这样解析日志时就会方便很多,最起码不用去兼容五花八门的日志格式了。
原文地址:https://www.cnblogs.com/cx2016/p/13029225.html