说到方法耗时统计、性能调试等,开发中经常都会去做,但我们在做这个事情的过程中,似乎很多人都用的很暴力的一种方式去做。哪里需要统计耗时,就直接使用时间戳变量,在最后计算结果,比如下面这样:
private static void work() {
long time1
= System
.currentTimeMillis();
workA();
long time2
= System
.currentTimeMillis();
workB();
long time3
= System
.currentTimeMillis();
workC();
long time4
= System
.currentTimeMillis();
workEnd();
long time5
= System
.currentTimeMillis();
long aRunTime
= time2
- time1
;
long bRunTime
= time3
- time2
;
long cRunTime
= time4
- time3
;
long totalRunTime
= time5
- time1
;
System
.out
.println("aRunTime:" + aRunTime
+ " bRunTime:" + bRunTime
+ " cRunTime:" + cRunTime
+ " totalRunTime:" + totalRunTime
);
}
有没有点似曾相识?这种做法我们同事都经常干,哈哈哈,简单的写两句图省事,但是往往不省事~唉。
在一个方法内我们可以写几个局部变量统计,多个方法我们写几个成员变量统计,不在一个类,页面跳转之类的又怎么办呢?写几个全局静态变量来统计么?
我个人是不太喜欢这样的写法,感觉会越写越乱,而且变得很麻烦、复杂,也不好控制和查看,所以最后写个简单方便的小工具。
使用效果图如下:
解释一波
第一行包含标签,表示开始。最后一行统计总耗时,以及添加记录的次数,表示结束。中间部分就是具体的每次记录。举例:1492 ms message: 执行A。
1492 ms(毫秒)是相对于前一条记录的耗时。“message: 执行A” 中的 “执行A” 是任意添加的消息内容。任务A执行耗时1492毫秒,任务B执行耗时0毫秒(耗时太少,毫秒单位统计不出来)。任意两个记录间的耗时可以相加,得到间隔的耗时,比如:从开始到任务C的耗时 = 1492 + 2004 = 3496ms。
代码实现
import java
.util
.ArrayList
;
import java
.util
.List
;
import java
.util
.concurrent
.ConcurrentHashMap
;
public class TimingRecorder {
private static String TAG
= "TimingRecorder";
private static boolean isToLog
= true;
private static final int RECORD_MAX_NUM
= 1000;
private static final ConcurrentHashMap
<String
, List
<RecordTime>> recordMap
= new ConcurrentHashMap<>();
public static void addRecord(String label
, String message
) {
RecordTime recordTime
= new RecordTime();
recordTime
.time
= System
.nanoTime() / 1000_000
;
recordTime
.message
= message
;
handleRecordTimes(label
, recordTime
);
}
private static synchronized void handleRecordTimes(String label
, RecordTime recordTime
) {
List
<RecordTime> recordTimes
= getRecordTimes(label
);
if (recordTimes
.size() >= RECORD_MAX_NUM
) {
String desc
= label
+ ": Add too many records! will invoke logTime.";
println(desc
);
RecordTime firstRecordTime
= recordTimes
.get(0);
logTime(label
, desc
);
recordTimes
= getRecordTimes(label
);
recordTimes
.add(firstRecordTime
);
}
recordTimes
.add(recordTime
);
}
private static List
<RecordTime> getRecordTimes(String label
) {
List
<RecordTime> recordTimes
= recordMap
.get(label
);
if (recordTimes
== null
) {
recordTimes
= new ArrayList<>();
List
<RecordTime> tempRecords
= recordMap
.putIfAbsent(label
, recordTimes
);
if (tempRecords
!= null
) {
recordTimes
= tempRecords
;
}
}
return recordTimes
;
}
public static String
logTime(String label
) {
return logTime(label
, null
);
}
public static String
logTime(String label
, String msg
) {
List
<RecordTime> recordTimes
;
synchronized (TimingRecorder
.class) {
recordTimes
= recordMap
.remove(label
);
}
if (recordTimes
!= null
&& recordTimes
.size() < RECORD_MAX_NUM
) {
RecordTime recordTime
= new RecordTime();
recordTime
.time
= System
.nanoTime() / 1000_000
;
recordTime
.message
= msg
!= null
? msg
: "log time end.";
recordTimes
.add(recordTime
);
}
String record
= parseRecord(label
, recordTimes
);
if (isToLog
) {
println(record
);
}
return record
;
}
private static String
parseRecord(String label
, List
<RecordTime> recordTimes
) {
StringBuilder buffer
= new StringBuilder();
buffer
.append("\n标签: ").append(label
);
if (recordTimes
== null
|| recordTimes
.isEmpty()) {
buffer
.append(" <没有记录>");
return buffer
.toString();
}
buffer
.append(" <开始>\n");
int size
= recordTimes
.size();
long firstTime
= recordTimes
.get(0).time
;
long lastTime
= recordTimes
.get(size
- 1).time
;
long totalTime
= lastTime
- firstTime
;
long prevTime
= firstTime
;
int maxDigitLength
= String
.valueOf(totalTime
).length();
for (int i
= 0; i
< size
; i
++) {
RecordTime record
= recordTimes
.get(i
);
long spaceTime
= record
.time
- prevTime
;
prevTime
= record
.time
;
int currentDigitLength
= String
.valueOf(spaceTime
).length();
String indentSpaceStr
= getSpaceStr(maxDigitLength
- currentDigitLength
);
buffer
.append(indentSpaceStr
);
buffer
.append(spaceTime
).append(" ms");
buffer
.append(" message: ").append(record
.message
);
buffer
.append("\n");
}
buffer
.append("<结束>");
buffer
.append(" 总耗时: ").append(totalTime
).append(" ms");
buffer
.append(" 记录次数: ").append(size
).append(" 次");
return buffer
.toString();
}
private static class RecordTime {
long time
;
String message
;
}
private static String
getSpaceStr(int spaces
) {
String number
= String
.valueOf(spaces
<= 0 ? "" : spaces
);
return String
.format("%" + number
+ "s", "");
}
private static void println(String msg
) {
System
.out
.println(msg
);
}
}
使用方式
TimingRecorder
.addRecord("label", "work start");
TimingRecorder
.addRecord("label", "work A");
TimingRecorder
.addRecord("label", "work B");
TimingRecorder
.addRecord("label", "work C");
TimingRecorder
.logTime("label", "work end");
达到的目的?
简单、易用,不用手动写时间戳再计算吧。可以统计很多方法函数的耗时,想统计几个写几个。以标签作为区别不同统计耗时的唯一键,好理解,可跨不同方法、不同类添加记录。一个统计记录在一条日志里,美观好看,不用到处查找其他记录。
注意事项
isToLog控制日志打印开关,正式环境不需要打印就置false关掉。logTime()方法返回统计记录的字符串,可以拿到数据丢到本地文件等。单个标签可以添加记录的最大数量是1000条,但不是说超过就不能用了,为了减少占内存,会先打印清空释放,和后续的统计数据可以串起来的。代码里记录时间用的System.nanoTime() / 1000_000,所以如果需要更精确的时间,比如微秒,就除以1000。调用日志打印:Java里用System.out,Android最好是用Log。
最后
看起来简单的一个小工具,其实考虑的东西还是有不少的,里面也有没考虑到的问题,大家可以根据自己需求修改完善。