欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

如何在Java中利用StopWatch类?

最编程 2024-08-08 17:26:42
...

以前本人在计算某个代码块执行速度的时候,通常是使用获取前后两个时间戳,然后计算差值得到执行时间,后来知道了还有一个专门用于计算执行的时间的码表类。

为何要用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;
  1. id 作为多个码表区分标识

  2. keepTaskList 开关是否保存执行过的任务列表,默认为true保存

  3. taskList 存储执行的计时任务列表

  4. startTimeMillis当前任务的开始时间

  5. running 判断当前是否在计时

  6. currentTaskName 当前任务的名称

  7. lastTaskInfo 最后一次执行的计时任务

  8. taskCount 累计执行的计时任务数量

  9. 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方法,与第二个相比,少了数据格式化,其他的没有差异。

推荐阅读