爱可深思▼ 2017▼ 内测 递归 统计 排序

A014.内测.统计工具类使用频度

有生产力的方法大多是基于分解的。分解在数据和方法的基础上, 更依赖于主观经验和解读,如数据直觉。我们从代码直觉开始。

@史荣久 / 2017-04-22 / CC-BY-SA-3.0

任务背景

缘起于想团队,讲解常用的工具类,比如 apache commons, google guava。
为此想简单统计了所有java文件中工具类的方法的使用情况。

简单的shell脚本不能满足这个需求,于是就敲了段代码。
敲了几行代码后,发现代码本身,别讲解工具类使用更有趣味。

如果扩展起来,可能还能涉及到java抽象语法树的解析(非静态类和方法)。

任务内容

自由发挥,根据自己情况选题和扩展,做一个代码​统计工具。

  • 统计有效代码行数(空白和注释不算)
  • 统计单词及词频(如空白,驼峰分词)
  • 统计某个方法的使用次数,如果可以
  • 按作者(@author)统计,如果有
  • 支持java,html,js至少一种

原型代码

先从代码基本功说起吧,思考下面原型代码中的12个Thinking。

package com.jiayu.common.recruit;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author trydofor
 * @since 2017-04-21
 */
public class CountStatic {

    private void count(File file, String pkg, Map<String, AtomicInteger> count) {
        // Thinking 1: 前置判断,使用其他方式如何?
        if (!file.getName().endsWith(".java")) return;

        // Thinking 2: try-catch-return,而不在方法上throws,使用其他方式如何?
        List<String> lines;
        try {
            lines = Files.readAllLines(file.toPath(), Charset.forName("UTF8"));
        } catch (IOException e) {
            return;
        }

        Map<String, String> map = new HashMap<>();
        boolean head = true;

        for (String line : lines) {
            // import
            // Thinking 3: 为何不用正则?
            if (line.startsWith("import ") && line.contains(pkg)) {
                int p0 = line.indexOf(pkg);
                int p1 = line.lastIndexOf('.') + 1;
                int p2 = line.lastIndexOf(';');
                String name = line.substring(p1, p2);
                String full = line.substring(p0, p2);
                map.put(full, name);
                continue;
            }

            // class
            // Thinking 4: 为何增加flag?
            if (head) {
                if (line.contains(" class ")) {
                    head = false;
                }
                continue;
            }

            // Class.method()
            // Thinking 5: 这么多 continue, for,使用其他方式如何?
            for (Map.Entry<String, String> entry : map.entrySet()) {
                int p1 = line.indexOf(entry.getValue());
                if (p1 < 0) continue;
                int p2 = line.indexOf('.', p1) + 1;
                if (p2 <= p1) continue;
                int p3 = line.indexOf('(', p2);
                if (p3 <= p2) continue;

                boolean jid = true;
                for (int i = p2; i < p3; i++) {
                    char c = line.charAt(i);
                    if (!Character.isJavaIdentifierPart(c)) {
                        jid = false;
                        break;
                    }
                }

                if (!jid) continue;

                String method = line.substring(p2, p3);

                String key = entry.getKey() + "." + method;

                // Thinking 6: 如何支持多线程下 *高效*,*可靠*的计数?
                AtomicInteger cnt = count.get(key);
                if (cnt == null) {
                    cnt = new AtomicInteger(1);
                    count.put(key, cnt);
                } else {
                    cnt.incrementAndGet();
                }
            }
        }
    }

    // Thinking 7: 递归有什么特点,递归和While循环有什么关联。
    private void walk(File file, String pkg, Map<String, AtomicInteger> count) {
        File[] files = file.listFiles();
        if (files == null) return;
        for (File f : files) {
            if (f.isDirectory()) {
                walk(f, pkg, count);
            } else {
                AtomicInteger cnt = count.get("total-files:");
                if (cnt == null) {
                    cnt = new AtomicInteger(1);
                    count.put("total-files:", cnt);
                } else {
                    cnt.incrementAndGet();
                }
                count(f, pkg, count);
            }
        }
    }

    // 统计某个目录下,所有 .java文件中 apache commons 静态工具方法使用次数
    public static void main(String[] args) throws IOException {

        String root = "/home/trydofor/Workspace/jiayu/source/";
        String pkg = "org.apache.commons.";

        Map<String, AtomicInteger> count = new HashMap<>();
        CountStatic countStatic = new CountStatic();
        countStatic.walk(new File(root), pkg, count);

        // Thinking 8: 如何按照包名自然字符串排序显示?
        // Thinking 9: 如何按照使用数量从大到小排序显示?

        // Thinking 10: 如何使用多线程统计?
        // Thinking 11: 如何写成多线程下的 map-reduce形式统计?

        for (Map.Entry<String, AtomicInteger> entry : count.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
    }
    // Thinking 12: 以上各个Thinking是不是有病?
}

《A014.内测.统计工具类使用频度》 有生产力的方法大多是基于分解的。分解在数据和方法的基础上,更依赖于主观经验和解读,如数据直觉。我们从代码直觉开始。
题图:和《蚁人》无关,和“蚂蚁吃大象”有关。处理大事情就像蚂蚁吃大象,也像盲人摸象 :)