摘要:實現先看實現之后的效果測試類運行輸出如下可以看到此時加了注解的和的運行時間被統計了,而沒加的未被統計在內。思路修改,在之前的中返回一個,儲存方法名耗時的鍵值結構。然后降序排序返回一個。最后遍歷根據百分比求得各個方法的并輸出相關信息。
最初目的
在學習Java的集合類時,有時候想要測試代碼塊的運行時間,以比較不同算法數據結構之間的性能差異。最簡單的做法是在代碼塊的前后記錄時間戳,最后相減得到該代碼塊的運行時間。
下面是Java中的示例:
public static void main(String[] args) { long start = System.currentTimeMillis(); algo(); // 執行代碼塊 long end = System.currentTimeMillis(); System.out.println(end - start); }
當需要同時打印多個方法的運行時間以進行比較的時候就會變成這樣:
public static void main(String[] args) { long start = System.currentTimeMillis(); algo1(); // 算法1 long end = System.currentTimeMillis(); System.out.println(end - start); long start = System.currentTimeMillis(); algo2(); // 算法2 long end = System.currentTimeMillis(); System.out.println(end - start); long start = System.currentTimeMillis(); algo3(); // 算法3 long end = System.currentTimeMillis(); System.out.println(end - start); // more }初探
顯然上面的代碼看起來非常冗余,由于Java不支持func(func)這樣的直接傳遞函數指針,本人又不想引入JDK以外太重的工具,所以嘗試寫一個回調來實現代碼塊的傳遞:
public interface Callback { void execute(); }
public class TimerUtil { public void getTime(Callback callback) { long start = System.currentTimeMillis(); callback.execute(); long end = System.currentTimeMillis(); System.out.println(end - start); } }
// 測試類 public class Foo { void algo1() { // algo1 } void algo2() { // algo2 } void algo3() { // algo3 } public static void main(String[] foo){ TimerUtil tu = new TimerUtil(); tu.getTime(new Callback() { @Override public void execute() { new Foo().algo1(); } }); tu.getTime(new Callback() { @Override public void execute() { new Foo().algo2(); } }); tu.getTime(new Callback() { @Override public void execute() { new Foo().algo3(); } }); } }
發現此時雖然封裝了計時、打印等業務無關的代碼,然而對使用者來說代碼量并沒有減少多少。若仔細觀察,其實測試類中仍有一堆結構重復的代碼,真正的業務藏在一堆匿名類中間,視覺上干擾很大。
Java 8為了解決類似的問題,引入了lambda,可以將代碼簡化為tu.getTime(() -> new Foo().algo());。lambda看起來很美,簡化了許多,然而這種寫法對于不熟悉的人寫起來還是不太順手,而且Java 8以下的環境無法這樣寫。
更重要的是從代碼的形式上看,algo() 還是被包在表達式內,仿佛getTime()才是主要邏輯一樣。由于之前接觸過Python,此時不禁想到,要是能像Python里那樣用裝飾器來解決就簡潔又方便了:
@getTime def algo1(): # algo1 @getTime def algo2(): # algo2
不過Java中也沒有這樣的語法糖,只有注解,于是思考是否可以利用反射和注解來“反轉”這種喧賓奪主的情況并使代碼更具可讀性。
實現先看實現之后的效果:
// 測試類Foo public class Foo { @Timer public void algo1() { ArrayListl = new ArrayList<>(); for (int i = 0; i < 10000000; i++) { l.add(1); } } @Timer public void algo2() { LinkedList l = new LinkedList<>(); for (int i = 0; i < 10000000; i++) { l.add(1); } } public void algo3() { Vector v = new Vector<>(); for (int i = 0; i < 10000000; i++) { v.add(1); } } public static void main(String[] foo){ TimerUtil tu = new TimerUtil(); tu.getTime(); } }
運行輸出如下:
可以看到此時加了@Timer注解的algo1()和algo2()的運行時間被統計了,而沒加@Timer的algo3()未被統計在內。
思路使用反射獲取棧中當前類(測試類)的信息,遍歷其中的方法,若方法包含@Timer注解,則執行該方法并進行時間戳相減。
實現這樣的效果僅需一個自定義注解和一個工具類:
@Retention(RetentionPolicy.RUNTIME) public @interface Timer { }
public class TimerUtil { public void getTime() { // 獲取當前類名 String className = Thread.currentThread().getStackTrace()[2].getClassName(); System.out.println("current className(expected): " + className); try { Class c = Class.forName(className); Object obj = c.newInstance(); Method[] methods = c.getDeclaredMethods(); for (Method m : methods) { // 判斷該方法是否包含Timer注解 if (m.isAnnotationPresent(Timer.class)) { m.setAccessible(true); long start = System.currentTimeMillis(); // 執行該方法 m.invoke(obj); long end = System.currentTimeMillis(); System.out.println(m.getName() + "() time consumed: " + String.valueOf(end - start) + " "); } } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }升級
在同時統計多個方法時,要是能可視化的打印出類似Performance Index一樣的柱狀圖,可以更直觀的比較他們之間的性能差異,就像這樣:
耗時最久的方法的Index固定為100,剩余的按相對的Index降序排列。
思路修改TimerUtil,在之前的getTime()中返回一個HashMap,儲存方法名: 耗時的鍵值結構。然后降序排序HashMap返回一個LinkedHashMap。最后遍歷LinkedHashMap根據百分比求得各個方法的Index并輸出相關信息。
public class TimerUtil { // 修改getTime() public HashMap總結getMethodsTable() { HashMap methodsTable = new HashMap<>(); String className = Thread.currentThread().getStackTrace()[3].getClassName(); // ... return methodsTable; } public void printChart() { Map result = sortByValue(getMethodsTable()); double max = result.values().iterator().next(); for (Map.Entry e : result.entrySet()) { double index = e.getValue() / max * 100; for (int i = 0; i < index; i++) { System.out.print("="); } System.out.println(e.getKey() + "()" + " Index:" + (long) index + " Time:" + e.getValue()); } } > Map sortByValue(Map map) { List > list = new LinkedList<>(map.entrySet()); // desc order Collections.sort(list, new Comparator >() { public int compare(Map.Entry o1, Map.Entry o2) { return (o2.getValue()).compareTo(o1.getValue()); } }); Map result = new LinkedHashMap<>(); for (Map.Entry entry : list) { result.put(entry.getKey(), entry.getValue()); } return result; } }
本文介紹的是一個APM (Algorithm Performance Measurement) 工具比較粗糙簡陋的實現,然而這種思路可以同樣應用在權限控制、日志、緩存等方面,方便的對代碼進行解耦,讓通用的功能“切入”原先的代碼,使得開發時可以更專注于業務邏輯。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65190.html
摘要:從使用到原理學習線程池關于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實現在軟件開發中,分散于應用中多出的功能被稱為橫切關注點如事務安全緩存等。 Java 程序媛手把手教你設計模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經風雨慢慢變老,回首走過的點點滴滴,依然清楚的記得當初愛情萌芽的模樣…… Java 進階面試問題列表 -...
摘要:幾乎每一個接口被調用后,都要記錄一條跟這個參數掛鉤的特定的日志到數據庫。我最終采用了的方式,采取攔截的請求的方式,來記錄日志。所有打上了這個注解的方法,將會記錄日志。那么如何從眾多可能的參數中,為當前的日志指定對應的參數呢。 前言 不久前,因為需求的原因,需要實現一個操作日志。幾乎每一個接口被調用后,都要記錄一條跟這個參數掛鉤的特定的日志到數據庫。舉個例子,就比如禁言操作,日志中需要記...
摘要:入門和學習筆記概述框架的核心有兩個容器作為超級大工廠,負責管理創建所有的對象,這些對象被稱為。中的一些術語切面切面組織多個,放在切面中定義。 Spring入門IOC和AOP學習筆記 概述 Spring框架的核心有兩個: Spring容器作為超級大工廠,負責管理、創建所有的Java對象,這些Java對象被稱為Bean。 Spring容器管理容器中Bean之間的依賴關系,使用一種叫做依賴...
閱讀 2001·2019-08-29 16:27
閱讀 1370·2019-08-29 16:14
閱讀 3372·2019-08-29 14:18
閱讀 3455·2019-08-29 13:56
閱讀 1252·2019-08-29 11:13
閱讀 2118·2019-08-28 18:19
閱讀 3439·2019-08-27 10:57
閱讀 2273·2019-08-26 11:39