摘要:實(shí)現(xiàn)先看實(shí)現(xiàn)之后的效果測(cè)試類運(yùn)行輸出如下可以看到此時(shí)加了注解的和的運(yùn)行時(shí)間被統(tǒng)計(jì)了,而沒加的未被統(tǒng)計(jì)在內(nèi)。思路修改,在之前的中返回一個(gè),儲(chǔ)存方法名耗時(shí)的鍵值結(jié)構(gòu)。然后降序排序返回一個(gè)。最后遍歷根據(jù)百分比求得各個(gè)方法的并輸出相關(guān)信息。
最初目的
在學(xué)習(xí)Java的集合類時(shí),有時(shí)候想要測(cè)試代碼塊的運(yùn)行時(shí)間,以比較不同算法數(shù)據(jù)結(jié)構(gòu)之間的性能差異。最簡單的做法是在代碼塊的前后記錄時(shí)間戳,最后相減得到該代碼塊的運(yùn)行時(shí)間。
下面是Java中的示例:
public static void main(String[] args) { long start = System.currentTimeMillis(); algo(); // 執(zhí)行代碼塊 long end = System.currentTimeMillis(); System.out.println(end - start); }
當(dāng)需要同時(shí)打印多個(gè)方法的運(yùn)行時(shí)間以進(jìn)行比較的時(shí)候就會(huì)變成這樣:
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)這樣的直接傳遞函數(shù)指針,本人又不想引入JDK以外太重的工具,所以嘗試寫一個(gè)回調(diào)來實(shí)現(xiàn)代碼塊的傳遞:
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); } }
// 測(cè)試類 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(); } }); } }
發(fā)現(xiàn)此時(shí)雖然封裝了計(jì)時(shí)、打印等業(yè)務(wù)無關(guān)的代碼,然而對(duì)使用者來說代碼量并沒有減少多少。若仔細(xì)觀察,其實(shí)測(cè)試類中仍有一堆結(jié)構(gòu)重復(fù)的代碼,真正的業(yè)務(wù)藏在一堆匿名類中間,視覺上干擾很大。
Java 8為了解決類似的問題,引入了lambda,可以將代碼簡化為tu.getTime(() -> new Foo().algo());。lambda看起來很美,簡化了許多,然而這種寫法對(duì)于不熟悉的人寫起來還是不太順手,而且Java 8以下的環(huán)境無法這樣寫。
更重要的是從代碼的形式上看,algo() 還是被包在表達(dá)式內(nèi),仿佛getTime()才是主要邏輯一樣。由于之前接觸過Python,此時(shí)不禁想到,要是能像Python里那樣用裝飾器來解決就簡潔又方便了:
@getTime def algo1(): # algo1 @getTime def algo2(): # algo2
不過Java中也沒有這樣的語法糖,只有注解,于是思考是否可以利用反射和注解來“反轉(zhuǎn)”這種喧賓奪主的情況并使代碼更具可讀性。
實(shí)現(xiàn)先看實(shí)現(xiàn)之后的效果:
// 測(cè)試類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(); } }
運(yùn)行輸出如下:
可以看到此時(shí)加了@Timer注解的algo1()和algo2()的運(yùn)行時(shí)間被統(tǒng)計(jì)了,而沒加@Timer的algo3()未被統(tǒng)計(jì)在內(nèi)。
思路使用反射獲取棧中當(dāng)前類(測(cè)試類)的信息,遍歷其中的方法,若方法包含@Timer注解,則執(zhí)行該方法并進(jìn)行時(shí)間戳相減。
實(shí)現(xiàn)這樣的效果僅需一個(gè)自定義注解和一個(gè)工具類:
@Retention(RetentionPolicy.RUNTIME) public @interface Timer { }
public class TimerUtil { public void getTime() { // 獲取當(dāng)前類名 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(); // 執(zhí)行該方法 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(); } } }升級(jí)
在同時(shí)統(tǒng)計(jì)多個(gè)方法時(shí),要是能可視化的打印出類似Performance Index一樣的柱狀圖,可以更直觀的比較他們之間的性能差異,就像這樣:
耗時(shí)最久的方法的Index固定為100,剩余的按相對(duì)的Index降序排列。
思路修改TimerUtil,在之前的getTime()中返回一個(gè)HashMap,儲(chǔ)存方法名: 耗時(shí)的鍵值結(jié)構(gòu)。然后降序排序HashMap返回一個(gè)LinkedHashMap。最后遍歷LinkedHashMap根據(jù)百分比求得各個(gè)方法的Index并輸出相關(guān)信息。
public class TimerUtil { // 修改getTime() public HashMap總結(jié)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; } }
本文介紹的是一個(gè)APM (Algorithm Performance Measurement) 工具比較粗糙簡陋的實(shí)現(xiàn),然而這種思路可以同樣應(yīng)用在權(quán)限控制、日志、緩存等方面,方便的對(duì)代碼進(jìn)行解耦,讓通用的功能“切入”原先的代碼,使得開發(fā)時(shí)可以更專注于業(yè)務(wù)邏輯。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/65190.html
摘要:攔截器攔截下那些沒有與注解標(biāo)注的方法請(qǐng)求,并進(jìn)行用戶認(rèn)證。直接根據(jù)編寫的代碼生成原生的代碼,所以不會(huì)存在任何性能問題解決方案為了解決攔截器中使用反射的性能問題,我們學(xué)習(xí)的設(shè)計(jì)思路,在啟動(dòng)時(shí)直接完成所有反射注解的讀取,存入內(nèi)存。 問題描述 權(quán)限認(rèn)證 權(quán)限認(rèn)證一直是比較復(fù)雜的問題,如果是實(shí)驗(yàn)這種要求不嚴(yán)格的產(chǎn)品,直接逃避掉權(quán)限認(rèn)證。 軟件設(shè)計(jì)與編程實(shí)踐的實(shí)驗(yàn),后臺(tái)直接用Spring Dat...
摘要:從使用到原理學(xué)習(xí)線程池關(guān)于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實(shí)現(xiàn)在軟件開發(fā)中,分散于應(yīng)用中多出的功能被稱為橫切關(guān)注點(diǎn)如事務(wù)安全緩存等。 Java 程序媛手把手教你設(shè)計(jì)模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經(jīng)風(fēng)雨慢慢變老,回首走過的點(diǎn)點(diǎn)滴滴,依然清楚的記得當(dāng)初愛情萌芽的模樣…… Java 進(jìn)階面試問題列表 -...
摘要:幾乎每一個(gè)接口被調(diào)用后,都要記錄一條跟這個(gè)參數(shù)掛鉤的特定的日志到數(shù)據(jù)庫。我最終采用了的方式,采取攔截的請(qǐng)求的方式,來記錄日志。所有打上了這個(gè)注解的方法,將會(huì)記錄日志。那么如何從眾多可能的參數(shù)中,為當(dāng)前的日志指定對(duì)應(yīng)的參數(shù)呢。 前言 不久前,因?yàn)樾枨蟮脑颍枰獙?shí)現(xiàn)一個(gè)操作日志。幾乎每一個(gè)接口被調(diào)用后,都要記錄一條跟這個(gè)參數(shù)掛鉤的特定的日志到數(shù)據(jù)庫。舉個(gè)例子,就比如禁言操作,日志中需要記...
摘要:入門和學(xué)習(xí)筆記概述框架的核心有兩個(gè)容器作為超級(jí)大工廠,負(fù)責(zé)管理創(chuàng)建所有的對(duì)象,這些對(duì)象被稱為。中的一些術(shù)語切面切面組織多個(gè),放在切面中定義。 Spring入門IOC和AOP學(xué)習(xí)筆記 概述 Spring框架的核心有兩個(gè): Spring容器作為超級(jí)大工廠,負(fù)責(zé)管理、創(chuàng)建所有的Java對(duì)象,這些Java對(duì)象被稱為Bean。 Spring容器管理容器中Bean之間的依賴關(guān)系,使用一種叫做依賴...
閱讀 2070·2019-08-29 16:27
閱讀 1421·2019-08-29 16:14
閱讀 3436·2019-08-29 14:18
閱讀 3520·2019-08-29 13:56
閱讀 1305·2019-08-29 11:13
閱讀 2197·2019-08-28 18:19
閱讀 3505·2019-08-27 10:57
閱讀 2349·2019-08-26 11:39