从 Spring PR 中学习代码技巧

从 Spring PR 中学习代码技巧

D瓜哥经常关注 Spring 的 PR 与 Issue。在众多 Contributor 中,除了 Spring 团队成员之外,我对 stsypanov (Сергей Цыпанов) 印象很深刻。这哥们给 Spring 提了非常多的 PR,请看列表 Pull requests · spring-projects/spring-framework,而且这个哥们的 PR 都非常有特点,绝大部分是性能提升方面的 PR,而且还会给出 JMH 的测试结果。不愧是毛熊人,做事细致严谨。

这周心血来潮,把这哥们的 PR 翻一翻,希望可以学习一些编码技巧。简单记录一下,以备以后回顾学习。

提高 Map 的遍历性能

摘取一个示例如下:

// --before update------------------------------------------------------
for (String attributeName : attributes.keySet()) {
    Object value = attributes.get(attributeName);

// --after update-------------------------------------------------------
for (Map.Entry<String, Object> attributeEntry : attributes.entrySet()) {
    String attributeName = attributeEntry.getKey();
    Object value = attributeEntry.getValue();

这个改动很小,但是对性能的改善还是比较显著的。翻看自己项目的代码,还是有不少是改动前的写法。

针对这点,D瓜哥也给 Spring 发了一个 PR: Improve performance of iteration in GroovyBeanDefinitionReader by diguage · Pull Request #27100。相信不久就会合并到 main 分支的。

所以,给 Spring 以及其他开源项目提 PR,其实一点也不难。只要,你花心思去研究,肯定有机会的。不过,也反思一点:我这个 PR 有点东施效颦的感觉,有点刷 KPI 的样子。还是应该脚踏实地去好好研究,提更多更有建设性意见的 PR。

StringJoiner

看这个 PR: Use StringJoiner where possible to simplify String joining by stsypanov · Pull Request #22430 才知道,原来在 Java 8 直接内置了 StringJoiner,翻看 StringJoiner 的源码,你会发现它出来可以设置连接符,竟然还可以设置前置符和后置符。后续也可以使用这个工具类。

ArrayList 初始化

从 PR 中摘录出两个修改片段:

// --before update-------------------------------------------
List<String> result = new ArrayList<>();
result.addAll(Arrays.asList(array1));

// --after update--------------------------------------------
List<String> result = new ArrayList<>(Arrays.asList(array1));
// --before update----------------------------------------
List<String> matchingHeaderNames = new ArrayList<>();
if (headers != null) {
    for (String key : headers.keySet()) {
        if (PatternMatchUtils.simpleMatch(pattern, key)) {
            matchingHeaderNames.add(key);
        }

// --after update-----------------------------------------
if (headers == null) {
    return Collections.emptyList();
}
List<String> matchingHeaderNames = new ArrayList<>();
for (String key : headers.keySet()) {
    if (PatternMatchUtils.simpleMatch(pattern, key)) {
        matchingHeaderNames.add(key);
    }

new ArrayList<>(Arrays.asList(array1))Collections.emptyList() 都是一些值得关注的代码小技巧。另外,在第二个修改片段中,直接进行空值判断,还可以减少下面代码的括号嵌套层数。

字符串连接

Simplify String concatenation by stsypanov · Pull Request #22466 这个 PR 改动很小,代码也乏善可陈。但是,在这个 PR 的描述中,Contributor 给出了这个 PR 的解释,里面给出的 Reference: StringBuffer and StringBuilder performance with JMH ,详细对比了不同情况下,“字符串连接”的性能情况,读一读还是有不少收获的。这里直接把文章结论引入过来:

  • StringBuilder is better than StringBuffer

  • StringBuilder.append(a).append(b) is better than StringBuilder.append(a+b)

  • StringBuilder.append(a).append(b) is better than StringBuilder.append(a); StringBuilder.append(b);

  • StringBuilder.append() and + are only equivalent provided that they are not nested and you don’t need to pre-sizing the builder

  • Pre-sizing the StringBuilder is like pre-sizing an ArrayList; if you know the approximate size you can reduce the garbage by specifying a capacity up-front

数组填充

// --before update----------------------
for (int i = 0; i < bytes.length; i++) {
    bytes[i] = 'h';
}

// --after update-----------------------
Arrays.fill(bytes, (byte) 'h');

用一行代码代替三行代码,何乐而不为呢?另外,估计很多人不知道 Arrays.fill(array, object); 这个 API。

Comparator

// --before update------------------------------------
Arrays.sort(ctors, (c1, c2) -> {
    int c1pl = c1.getParameterCount();
    int c2pl = c2.getParameterCount();
    return (c1pl < c2pl ? -1 : (c1pl > c2pl ? 1 : 0));
});

// --after update-------------------------------------
Arrays.sort(ctors, (c1, c2) -> {
    int c1pl = c1.getParameterCount();
    int c2pl = c2.getParameterCount();
    return Integer.compare(c1pl, c2pl);
});

Contributor 使用 Integer.compare(int, int) 来简化比较代码。所以,以后比较整数可以使用 Integer.compare(int, int)

其实,还可以更进一步:

// --before update----------------------------------------------------------
Arrays.sort(ctors, (c1, c2) -> {
    int c1pl = c1.getParameterCount();
    int c2pl = c2.getParameterCount();
    return Integer.compare(c1pl, c2pl);
});

// --after update-----------------------------------------------------------
Arrays.sort(ctors, Comparator.comparingInt(Constructor::getParameterCount));

数组克隆

// --before update--------------------------------
String[] copy = new String[state.length];
System.arraycopy(state, 0, copy, 0, state.length);
return copy;

// --after update---------------------------------
return state.clone();

复制数组,以前只知 System.arraycopy 可以高效完成任务,以后可以使用 array.clone()