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是不是有病?
}
题图:和《蚁人》无关,和“蚂蚁吃大象”有关。处理大事情就像蚂蚁吃大象,也像盲人摸象 :)