以前本人在计算某个代码块执行速度的时候,通常是使用获取前后两个时间戳,然后计算差值得到执行时间,后来知道了还有一个专门用于计算执行的时间的码表类。
为何要用StopWatch
首先说明这个StopWatch有什么便利之处,我们为什么使用它
其实最大的好处就是代码量少,现在比较最简单一个例子
import org.springframework.util.StopWatch;
public class TestStopWatch {
public static void main(String[] args) throws Exception{
testUseCurrentMills();
testUseCurrentStopWatch();
}
static void testUseCurrentMills() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(3000);
long end = System.currentTimeMillis();
System.out.println("执行时间"+(end - start));
}
static void testUseCurrentStopWatch()throws InterruptedException {
StopWatch stopWatch = new StopWatch("测试");
stopWatch.start();
Thread.sleep(3000);
stopWatch.stop();
System.out.println(stopWatch.shortSummary());
}
}
貌似看起来StopWatch比我们以前使用的还多一行,其实StopWatch优势在于对于多个代码块的计算,StopWatch可以提供单独的任务执行时间,以及任务的汇总时间,现在我们以计算三个代码块执行时间为例
import org.springframework.util.StopWatch;
public class TestStopWatch {
public static void main(String[] args) throws Exception{
testUseCurrentMills();
testUseCurrentStopWatch();
}
static void testUseCurrentMills() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(1000);
long end1 = System.currentTimeMillis();
System.out.println("代码块1执行时间"+(end1 - start));
Thread.sleep(2000);
long end2 = System.currentTimeMillis();
System.out.println("代码块2执行时间"+(end2 - end1));
Thread.sleep(3000);
long end3 = System.currentTimeMillis();
System.out.println("代码块3执行时间"+(end3 - end2));
System.out.println("总共执行时间"+(end3 - start));
}
static void testUseCurrentStopWatch()throws InterruptedException {
StopWatch stopWatch = new StopWatch("测试代码块组");
stopWatch.start("代码块1");
Thread.sleep(1000);
stopWatch.stop();
stopWatch.start("代码块2");
Thread.sleep(2000);
stopWatch.stop();
stopWatch.start("代码块3");
Thread.sleep(3000);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
}
代码块1执行时间1000
代码块2执行时间2001
代码块3执行时间3000
总共执行时间6001
StopWatch '测试代码块组': running time (millis) = 6002
-----------------------------------------
ms % Task name
-----------------------------------------
01001 017% 代码块1
02000 033% 代码块2
03001 050% 代码块3
通过上面代码比对,我们发现使用StopWatch,我们可以更加将注意力关注到实际业务代码上,只需要在业务代码的开始和结束,分别调用start()和stop()方法,而不用像以前一样,不断自己的手动获取时间,手动打印,最终所有任务结束后,也可以获取所有任务的执行时间,合计时间,以及耗时占比。StopWatch还有其他一些功能,我们可以通过阅读源代码来了解一些。
StopWatch类的使用
StopWatch 共有2个构造方法,1个内部类,9个字段。
/**
* Construct a new stop watch. Does not start any task.
*/
public StopWatch() {
this("");
}
/**
* Construct a new stop watch with the given id.
* Does not start any task.
* @param id identifier for this stop watch.
* Handy when we have output from multiple stop watches
* and need to distinguish between them.
*/
public StopWatch(String id) {
this.id = id;
}
这两个构造函数的区别在于,有参构造会把传入的字符串作为这个码表的名称表示,增加易读性,在打印信息时会把这个id加上。
/**
* Identifier of this stop watch.
* Handy when we have output from multiple stop watches
* and need to distinguish between them in log or console output.
*/
private final String id;
private boolean keepTaskList = true;
private final List<TaskInfo> taskList = new LinkedList<TaskInfo>();
/** Start time of the current task */
private long startTimeMillis;
/** Is the stop watch currently running? */
private boolean running;
/** Name of the current task */
private String currentTaskName;
private TaskInfo lastTaskInfo;
private int taskCount;
/** Total running time */
private long totalTimeMillis;
-
id 作为多个码表区分标识
-
keepTaskList 开关是否保存执行过的任务列表,默认为true保存
-
taskList 存储执行的计时任务列表
-
startTimeMillis当前任务的开始时间
-
running 判断当前是否在计时
-
currentTaskName 当前任务的名称
-
lastTaskInfo 最后一次执行的计时任务
-
taskCount 累计执行的计时任务数量
-
totalTimeMillis 所有任务累计耗时
public static final class TaskInfo { private final String taskName; private final long timeMillis; TaskInfo(String taskName, long timeMillis) { this.taskName = taskName; this.timeMillis = timeMillis; } /** * Return the name of this task. */ public String getTaskName() { return this.taskName; } /** * Return the time in milliseconds this task took. */ public long getTimeMillis() { return this.timeMillis; } /** * Return the time in seconds this task took. */ public double getTimeSeconds() { return (this.timeMillis / 1000.0); } }
计时任务包含2个属性,一个是名称,一个是耗时,方法也很简单,大家一看就明白
接下来看StopWatch 的具体方法(对于getter和setter方法就不做讲解)
/**
* Start an unnamed task. The results are undefined if {@link #stop()}
* or timing methods are called without invoking this method.
* @see #stop()
*/
public void start() throws IllegalStateException {
start("");
}
/**
* Start a named task. The results are undefined if {@link #stop()}
* or timing methods are called without invoking this method.
* @param taskName the name of the task to start
* @see #stop()
*/
public void start(String taskName) throws IllegalStateException {
if (this.running) {
throw new IllegalStateException("Can't start StopWatch: it's already running");
}
this.running = true;
this.currentTaskName = taskName;
this.startTimeMillis = System.currentTimeMillis();
}
start有2个重载方法,调用无参的那个,实际会自动赋值一个空串调用有参start方法
有参方法进来会首先判断当前是否已在计时,如果是,则抛出异常。
接下来回下盖计时状态为开始计时,将传递进来的参数作为最近计时任务的名称存储
以及获取当前时间戳作为当前计时任务的开始时间
/**
* Stop the current task. The results are undefined if timing
* methods are called without invoking at least one pair
* {@code start()} / {@code stop()} methods.
* @see #start()
*/
public void stop() throws IllegalStateException {
if (!this.running) {
throw new IllegalStateException("Can't stop StopWatch: it's not running");
}
long lastTime = System.currentTimeMillis() - this.startTimeMillis;
this.totalTimeMillis += lastTime;
this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
if (this.keepTaskList) {
this.taskList.add(lastTaskInfo);
}
++this.taskCount;
this.running = false;
this.currentTaskName = null;
}
stop方法进来会判断是否在计时,如果没有,也会抛出状态异常
然后通过获取当前时间戳减去我们start方法存储的开始时间戳得到本次任务耗时
同时将本次耗时累加到码表的任务合计耗时
如果保存计时任务属性为true,将本次任务加入到任务列表
累加任务数量,将计时状态设置为否,清空当前任务名称
这里源码作者说了,start和stop方法一定是成对出现的,否则一定会抛出异常
/**
* Return a short description of the total running time.
*/
public String shortSummary() {
return "StopWatch '" + getId() + "': running time (millis) = " + getTotalTimeMillis();
}
/**
* Return a string with a table describing all tasks performed.
* For custom reporting, call getTaskInfo() and use the task info directly.
*/
public String prettyPrint() {
StringBuilder sb = new StringBuilder(shortSummary());
sb.append('\n');
if (!this.keepTaskList) {
sb.append("No task info kept");
}
else {
sb.append("-----------------------------------------\n");
sb.append("ms % Task name\n");
sb.append("-----------------------------------------\n");
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumIntegerDigits(5);
nf.setGroupingUsed(false);
NumberFormat pf = NumberFormat.getPercentInstance();
pf.setMinimumIntegerDigits(3);
pf.setGroupingUsed(false);
for (TaskInfo task : getTaskInfo()) {
sb.append(nf.format(task.getTimeMillis())).append(" ");
sb.append(pf.format(task.getTimeSeconds() / getTotalTimeSeconds())).append(" ");
sb.append(task.getTaskName()).append("\n");
}
}
return sb.toString();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(shortSummary());
if (this.keepTaskList) {
for (TaskInfo task : getTaskInfo()) {
sb.append("; [").append(task.getTaskName()).append("] took ").append(task.getTimeMillis());
long percent = Math.round((100.0 * task.getTimeSeconds()) / getTotalTimeSeconds());
sb.append(" = ").append(percent).append("%");
}
}
else {
sb.append("; no task info kept");
}
return sb.toString();
}
StopWatch 提供了3个输出方法
第一个shortSummary 只会输出当前码表id和累计耗时,第二个prettyPrint在第一个的基础上拼接了每一个task的耗时毫秒,耗时占比,任务名称,这个比较常用,最后一个是toString方法,与第二个相比,少了数据格式化,其他的没有差异。