成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java 項(xiàng)目優(yōu)化實(shí)戰(zhàn)

wqj97 / 2870人閱讀

摘要:是一款用來分析應(yīng)用的圖形工具,能夠?qū)?yīng)用程序做性能分析和調(diào)優(yōu)。計(jì)算特征點(diǎn)特征點(diǎn)根據(jù)特征點(diǎn),計(jì)算對(duì)應(yīng)的如果只有一個(gè)元素,說明沒有特征值優(yōu)化二背景接下來解決第二個(gè)函數(shù)。成果經(jīng)過這兩個(gè)主要的優(yōu)化,就解決了代碼中的性能問題,成果如下圖所示

本文作者:CODING 工程師 Tan He

1 Visual VM

項(xiàng)目中的某一個(gè)接口,在某一場景下(數(shù)據(jù)量大),性能讓人難以忍受。

那么如何有什么工具可以定位引發(fā)性能問題的代碼呢?其實(shí)有很多,這里我們使用 Visual VM。

Visual VM 是一款用來分析 Java 應(yīng)用的圖形工具,能夠?qū)?Java 應(yīng)用程序做性能分析和調(diào)優(yōu)。如果你使用的 java 7 或者 java 8,那么可以直接在 JDK 的 bin 目錄找到該工具,名稱為 jvisualvm。當(dāng)然也可以在官網(wǎng)上自行下載。

使用 Visual VM 分析某個(gè)接口的性能的方法如下:

結(jié)果顯示如下:

通過上圖,我們可以看到比較耗時(shí)的方法為 resolveBytePosition 和 rest,getFile 和 currentUser 是網(wǎng)絡(luò)請(qǐng)求,暫不考慮。

2 優(yōu)化一 2.1 背景

首先拿 resolveBytePosition 方法開刀。為了能更容易的解釋 resolveBytePosition 的用途,舉個(gè)例子。

給定一個(gè)字符串 chars 與該字符串的 UTF-8 二進(jìn)制數(shù)組(空格用來隔開字符數(shù)據(jù),實(shí)際并不存在):

chars = "just一個(gè)test";
bytes = "6A 75 73 74 E4B880 E4B8AA 74 65 73 74";

resolveBytePosition 用來解決給定一個(gè) bytes 的偏移 bytePos 計(jì)算 chars 中的偏移 charPos 的問題。比如:

bytePos = 0 (6A) 對(duì)應(yīng) charPos = 0 (j)
bytePos = 1 (75) 對(duì)應(yīng) charPos = 1 (u)

如果使用 array[start:] 表示從下標(biāo) start 開始截取數(shù)組元素至末尾組成的新數(shù)組,那么則有:

bytes[bytePos:] = chars[charPos:]

舉例:

bytes[0:] = chars[0:]
bytes[1:] = chars[1:]
bytes[10:] = chars[6:]
2.2 原實(shí)現(xiàn)

明白了 resolveBytePosition 的作用,看一下它的實(shí)現(xiàn)

public int resolveBytePosition(byte[] bytes, int bytePos) {
    return new String(slice(bytes, 0, bytePos)).length();
}

該解法簡單粗暴,能夠準(zhǔn)確的計(jì)算出結(jié)果,但是缺點(diǎn)顯而易見,頻繁的構(gòu)建字符串,對(duì)性能造成了極大的影響。通過 Visual VM 可以證實(shí)我們的推論,通過點(diǎn)擊快照,查看更詳細(xì)的方法調(diào)用耗時(shí)。

2.3 剖析

為了更方便的剖析問題,我們繪制如下表格,用來展示每一個(gè)字符的 UTF-8 以及 Unicode 的二進(jìn)制數(shù)據(jù):

j u s t 個(gè) 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

接著我們將字節(jié)數(shù)據(jù)轉(zhuǎn)換為字節(jié)長度:

j u s t 個(gè) 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 個(gè)字節(jié),因此一個(gè) char 足以表示示例中的任何一個(gè)字符。

我們使用一個(gè)單元格表示一個(gè)byte(UTF-8)或一個(gè)char(Unicode),并對(duì)單元格編號(hào),得到下表:

? j u s t 個(gè) t e s t
bytes 0 1 2 3 4 5 6 7 8 9101112 13
chars 0 1 2 3 4 5 6 7 8 9

可以得出下面對(duì)應(yīng)關(guān)系

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 方案

進(jìn)行到這一步,高效的算法已經(jīng)呼之欲出了。算法如下:

把字符 UTF-8 數(shù)據(jù)的二進(jìn)制長度不為 1 的稱為特征點(diǎn)。除特征點(diǎn)外,每個(gè)字符都是一個(gè)字節(jié)長度。記下所有特征點(diǎn)的對(duì)應(yīng)關(guān)系,對(duì)于給定的 bytePos,都可以根據(jù)公式計(jì)算得到 charPos。

公式為:

charPos = bytePos - preBytePos + preCharPos

舉例:

則本實(shí)例中有兩個(gè)特征點(diǎn) 、個(gè),記作:

bytes[6:] = chars[4:]
bytes[9:] = chars[5:]

如果給定 bytePos 10, 首先找到前一個(gè)特征點(diǎn)的對(duì)應(yīng)關(guān)系 9(preBytePos) -> 5(preCharPos), 根據(jù)公式得出 (10 - 9) + 5 = 6。

2.4 核心代碼

該算法還有一個(gè)比較關(guān)鍵的問題要解決,即高效的計(jì)算一個(gè) char 的字節(jié)長度。計(jì)算 char 的字節(jié)長度的算法參考了 StackOverflow。

// 計(jì)算特征點(diǎn)
private int[][] calcSpecialPos(String str) {
    ArrayList 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) { // 特征點(diǎn)
            specialPos.add(new int[]{lastBytePos, lastCharPost});
        }
    }


    return toArray(specialPos);
}

// 根據(jù)特征點(diǎn),計(jì)算 bytePos 對(duì)應(yīng)的 charPos
private int calcPos(int[][] specialPos, int bytePos) {
    // 如果只有一個(gè)元素 {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];
    }
}
3 優(yōu)化二 3.1 背景

接下來解決第二個(gè)函數(shù) rest。該函數(shù)的功能是得到 JsonArray(gson) 的除第一個(gè)元素外的所有元素。

由于 rest 是在一個(gè)遞歸函數(shù)中被調(diào)用且遞歸棧很深,因此如果 rest 實(shí)現(xiàn)的不夠高效,其影響會(huì)被成倍放大。

3.2 原實(shí)現(xiàn)
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 剖析

通過調(diào)試發(fā)現(xiàn) JsonArray 中存儲(chǔ)了相當(dāng)大的數(shù)據(jù),對(duì)于頻繁調(diào)用的場景,每次都對(duì)其重新構(gòu)建明顯不是一個(gè)明智的選擇。
通過查看返回的 JsonArray 使用情況,我們得到了另一條線索:僅僅使用里面的數(shù)據(jù),而不涉及修改。

考慮到 JsonArray 被實(shí)現(xiàn)成 final,最后方案確定為實(shí)現(xiàn)一個(gè)針對(duì) rest 這種需求定制的代理類。

3.4 方案 & 代碼

代理類 JsonArrayWrapper 分別對(duì) first、rest、foreach 等功能進(jìn)行了實(shí)現(xiàn)。

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 action) {
        for (int i=mark; i
4 成果

經(jīng)過這兩個(gè)主要的優(yōu)化,就解決了代碼中的性能問題,成果如下圖所示:

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/65717.html

相關(guān)文章

  • 做IT這幾年,我整理了這些干貨想要送給你!

    摘要:資源獲取方式根據(jù)下面的索引,大家可以選擇自己需要的資源,然后在松哥公眾號(hào)牧碼小子后臺(tái)回復(fù)對(duì)應(yīng)的口令,就可以獲取到資源的百度云盤下載地址。公眾號(hào)二維碼如下另外本文會(huì)定期更新,松哥有新資源的時(shí)候會(huì)及時(shí)分享給大家,歡迎各位小伙伴保持關(guān)注。 沒有一條路是容易的,特別是轉(zhuǎn)行計(jì)算機(jī)這條路。 松哥接觸過很多轉(zhuǎn)行做開發(fā)的小伙伴,我了解到很多轉(zhuǎn)行人的不容易,記得松哥大二時(shí)剛剛決定轉(zhuǎn)行計(jì)算機(jī),完全不知道這...

    王晗 評(píng)論0 收藏0
  • Android程序員完全沒時(shí)間提升自己怎么辦?

    摘要:昨天有個(gè)小學(xué)弟給我發(fā)來微信,說他現(xiàn)在有點(diǎn)后悔選擇開發(fā)了,月月光不說,還加班特別嚴(yán)重,平時(shí)也沒有屬于自己的時(shí)間去學(xué)習(xí),問我剛畢業(yè)的時(shí)候是不是這樣。每天回到出租屋都是倒頭就睡,非常累,也沒有其他時(shí)間提升自己的技術(shù)。 昨天有個(gè)小學(xué)弟給我發(fā)來微信,說他現(xiàn)在有點(diǎn)后悔選擇Android開發(fā)了,月月光不說...

    kohoh_ 評(píng)論0 收藏0
  • 【ShareBook】1-后臺(tái)框架與小程序用戶登錄接口實(shí)戰(zhàn)

    摘要:注冊(cè)流程是從小程序簡稱,以下替代獲取用戶的,給到服務(wù)器,服務(wù)器會(huì)用還有自己的等信息一起去微信服務(wù)器請(qǐng)求用戶數(shù)據(jù),注意每一個(gè)所對(duì)應(yīng)的用戶都是不一樣的。 本博客 貓叔的博客,轉(zhuǎn)載請(qǐng)申明出處閱讀本文約 5分鐘適讀人群:Java后端、Java初級(jí)、小程序前端 前后端項(xiàng)目的地址 ShareBookServer ShareBookClient 小程序前端 showImg(https://seg...

    zorro 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<