摘要:是一款用來分析應用的圖形工具,能夠對應用程序做性能分析和調優。計算特征點特征點根據特征點,計算對應的如果只有一個元素,說明沒有特征值優化二背景接下來解決第二個函數。成果經過這兩個主要的優化,就解決了代碼中的性能問題,成果如下圖所示
1 Visual VM本文作者:CODING 工程師 Tan He
項目中的某一個接口,在某一場景下(數據量大),性能讓人難以忍受。
那么如何有什么工具可以定位引發性能問題的代碼呢?其實有很多,這里我們使用 Visual VM。
Visual VM 是一款用來分析 Java 應用的圖形工具,能夠對 Java 應用程序做性能分析和調優。如果你使用的 java 7 或者 java 8,那么可以直接在 JDK 的 bin 目錄找到該工具,名稱為 jvisualvm。當然也可以在官網上自行下載。
使用 Visual VM 分析某個接口的性能的方法如下:
結果顯示如下:
通過上圖,我們可以看到比較耗時的方法為 resolveBytePosition 和 rest,getFile 和 currentUser 是網絡請求,暫不考慮。
2 優化一 2.1 背景首先拿 resolveBytePosition 方法開刀。為了能更容易的解釋 resolveBytePosition 的用途,舉個例子。
給定一個字符串 chars 與該字符串的 UTF-8 二進制數組(空格用來隔開字符數據,實際并不存在):
chars = "just一個test"; bytes = "6A 75 73 74 E4B880 E4B8AA 74 65 73 74";
resolveBytePosition 用來解決給定一個 bytes 的偏移 bytePos 計算 chars 中的偏移 charPos 的問題。比如:
bytePos = 0 (6A) 對應 charPos = 0 (j) bytePos = 1 (75) 對應 charPos = 1 (u)
如果使用 array[start:] 表示從下標 start 開始截取數組元素至末尾組成的新數組,那么則有:
bytes[bytePos:] = chars[charPos:]
舉例:
bytes[0:] = chars[0:] bytes[1:] = chars[1:] bytes[10:] = chars[6:]2.2 原實現
明白了 resolveBytePosition 的作用,看一下它的實現
public int resolveBytePosition(byte[] bytes, int bytePos) { return new String(slice(bytes, 0, bytePos)).length(); }
該解法簡單粗暴,能夠準確的計算出結果,但是缺點顯而易見,頻繁的構建字符串,對性能造成了極大的影響。通過 Visual VM 可以證實我們的推論,通過點擊快照,查看更詳細的方法調用耗時。
2.3 剖析為了更方便的剖析問題,我們繪制如下表格,用來展示每一個字符的 UTF-8 以及 Unicode 的二進制數據:
j | u | s | t | 一 | 個 | t | e | s | t | |
---|---|---|---|---|---|---|---|---|---|---|
UTF-8 | 6A | 75 | 73 | 74 | E4B880 | E4B8AA | 74 | 65 | 73 | 74 |
Unicode | 6A | 75 | 73 | 74 | 4E00 | 4E2A | 74 | 65 | 73 | 74 |
接著我們將字節數據轉換為字節長度:
j | u | s | t | 一 | 個 | t | e | s | t | |
---|---|---|---|---|---|---|---|---|---|---|
UTF-8 | 1 | 1 | 1 | 1 | 3 | 3 | 1 | 1 | 1 | 1 |
Unicode | 1 | 1 | 1 | 1 | 2 | 2 | 1 | 1 | 1 | 1 |
Java中的使用 char 來表示Unicode,char 的長度為 2 個字節,因此一個 char 足以表示示例中的任何一個字符。
我們使用一個單元格表示一個byte(UTF-8)或一個char(Unicode),并對單元格編號,得到下表:
? | j | u | s | t | 一 | 個 | t | e | s | t | ||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
bytes | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | chars | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
可以得出下面對應關系
bytes[0:] = chars[0:] bytes[1:] = chars[1:] bytes[2:] = chars[2:] bytes[3:] = chars[3:] bytes[4:] = chars[4:] bytes[7:] = chars[5:] bytes[10:] = chars[6:] ... ...2.3 方案
進行到這一步,高效的算法已經呼之欲出了。算法如下:
把字符 UTF-8 數據的二進制長度不為 1 的稱為特征點。除特征點外,每個字符都是一個字節長度。記下所有特征點的對應關系,對于給定的 bytePos,都可以根據公式計算得到 charPos。
公式為:
charPos = bytePos - preBytePos + preCharPos
舉例:
則本實例中有兩個特征點 一、個,記作:
bytes[6:] = chars[4:] bytes[9:] = chars[5:]
如果給定 bytePos 10, 首先找到前一個特征點的對應關系 9(preBytePos) -> 5(preCharPos), 根據公式得出 (10 - 9) + 5 = 6。
2.4 核心代碼該算法還有一個比較關鍵的問題要解決,即高效的計算一個 char 的字節長度。計算 char 的字節長度的算法參考了 StackOverflow。
// 計算特征點 private int[][] calcSpecialPos(String str) { ArrayList3 優化二 3.1 背景specialPos = new ArrayList<>() specialPos.add(new int[] {0, 0}); int lastCharPost = 0; int lastBytePos = 0; Charset utf8 = Charset.forName("UTF-8"); CharsetEncoder encoder = utf8.newEncoder(); CharBuffer input = CharBuffer.wrap(str.toCharArray()); ByteBuffer output = ByteBuffer.allocate(10); int limit = input.limit(); while(input.position() < limit) { output.clear(); input.mark(); input.limit(Math.min(input.position() + 2, input.capacity())); if (Character.isHighSurrogate(input.get()) && !Character.isLowSurrogate(input.get())) { //Malformed surrogate pair lastCharPost++; } input.limit(input.position()); input.reset(); encoder.encode(input, output, false); int encodedLen = output.position(); lastCharPost++; lastBytePos += encodedLen; if (encodedLen != 1) { // 特征點 specialPos.add(new int[]{lastBytePos, lastCharPost}); } } return toArray(specialPos); } // 根據特征點,計算 bytePos 對應的 charPos private int calcPos(int[][] specialPos, int bytePos) { // 如果只有一個元素 {0, 0),說明沒有特征值 if (specialPos.length == 1) return bytePos; int pos = Arrays.binarySearch(specialPos, new int[] {bytePos, 0}, (int[] a, int[] b) -> Integer.compare(a[0], b[0])); if (pos >= 0) { return specialPos[pos][1]; } else { // if binary search not fonund, will return (-(insertion point) - 1), // so here -2 is mean -1 to get insertpoint and then -1 to get previous specialPos int[] preSpecialPos = specialPos[-pos-2]; return bytePos - preSpecialPos[0] + preSpecialPos[1]; } }
接下來解決第二個函數 rest。該函數的功能是得到 JsonArray(gson) 的除第一個元素外的所有元素。
由于 rest 是在一個遞歸函數中被調用且遞歸棧很深,因此如果 rest 實現的不夠高效,其影響會被成倍放大。
3.2 原實現private JsonArray rest(JsonArray arr) { JsonArray result = new JsonArray(); if (arr.size() > 1) { for (int i = 1; i < arr.size(); i++) { result.add(arr.get(i)); } } return result; }3.3 剖析
通過調試發現 JsonArray 中存儲了相當大的數據,對于頻繁調用的場景,每次都對其重新構建明顯不是一個明智的選擇。
通過查看返回的 JsonArray 使用情況,我們得到了另一條線索:僅僅使用里面的數據,而不涉及修改。
考慮到 JsonArray 被實現成 final,最后方案確定為實現一個針對 rest 這種需求定制的代理類。
3.4 方案 & 代碼代理類 JsonArrayWrapper 分別對 first、rest、foreach 等功能進行了實現。
class JsonArrayWrapper implements Iterable{ private JsonArray jsonArray; private int mark; public JsonArrayWrapper() { this.jsonArray = new JsonArray(); this.mark = 0; } public JsonArrayWrapper(JsonArray jsonArray) { this.jsonArray= jsonArray; this.mark = 0; } public JsonArrayWrapper(JsonArray jsonArray, int mark) { this.jsonArray = jsonArray; this.mark = mark; } public JsonObject first() { return jsonArray.get(mark).getAsJsonObject(); } public JsonArrayWrapper rest() { return new JsonArrayWrapper(jsonArray, mark+1); } public int size() { return jsonArray.size() - mark; } public JsonElement get(int n) { return jsonArray.get(mark + n); } public void add(JsonElement jsonElement) { jsonArray.add(jsonElement); } public void addAll(JsonArrayWrapper jsonArrayWrapper) { jsonArrayWrapper.forEach(this.jsonArray::add); } @Override public Iterator iterator() { JsonArray jsonarray = new JsonArray(); this.forEach(e -> jsonarray.add(e)); return jsonarray.iterator(); } @Override public void forEach(Consumer super JsonElement> action) { for (int i=mark; i 4 成果 經過這兩個主要的優化,就解決了代碼中的性能問題,成果如下圖所示:
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65717.html
摘要:資源獲取方式根據下面的索引,大家可以選擇自己需要的資源,然后在松哥公眾號牧碼小子后臺回復對應的口令,就可以獲取到資源的百度云盤下載地址。公眾號二維碼如下另外本文會定期更新,松哥有新資源的時候會及時分享給大家,歡迎各位小伙伴保持關注。 沒有一條路是容易的,特別是轉行計算機這條路。 松哥接觸過很多轉行做開發的小伙伴,我了解到很多轉行人的不容易,記得松哥大二時剛剛決定轉行計算機,完全不知道這...
摘要:昨天有個小學弟給我發來微信,說他現在有點后悔選擇開發了,月月光不說,還加班特別嚴重,平時也沒有屬于自己的時間去學習,問我剛畢業的時候是不是這樣。每天回到出租屋都是倒頭就睡,非常累,也沒有其他時間提升自己的技術。 昨天有個小學弟給我發來微信,說他現在有點后悔選擇Android開發了,月月光不說...
摘要:注冊流程是從小程序簡稱,以下替代獲取用戶的,給到服務器,服務器會用還有自己的等信息一起去微信服務器請求用戶數據,注意每一個所對應的用戶都是不一樣的。 本博客 貓叔的博客,轉載請申明出處閱讀本文約 5分鐘適讀人群:Java后端、Java初級、小程序前端 前后端項目的地址 ShareBookServer ShareBookClient 小程序前端 showImg(https://seg...
閱讀 2772·2021-10-14 09:42
閱讀 827·2021-10-11 10:57
閱讀 773·2019-08-30 15:54
閱讀 1914·2019-08-30 13:50
閱讀 1686·2019-08-30 11:19
閱讀 932·2019-08-29 12:38
閱讀 1425·2019-08-26 11:51
閱讀 1388·2019-08-26 10:48