• Chào bạn, hãy đăng ký hoặc đăng nhập để tham gia cùng bọn mình và sử dụng được đầy đủ chức năng của diễn đàn :).
anhcraft

Bukkit [Optimization] Lưu trữ kết quả ChatColor#translateAlternateColorCodes

anhcraft

Thành viên BQT
DEVELOPER
THÀNH VIÊN
Tham gia
18/09/2016
Bài viết
3,165
Yay bắt đầu vào topic với vấn đề đầu tiên :D: Lưu trữ ChatColor#translateAlternateColorCodes để sử dụng lại
ChatColor#translateAlternateColorCodes là cái hàm mà các bạn hay dùng để chuyển kí tự & thành kí tự § để khi nhìn vào Minecraft sẽ hiện màu.

I. VẤN ĐỀ
Vấn đề ở đây là cái hàm này khá "nặng"...............
Vd tưởng tượng đi, bạn phải lm cái plugin có chức năng gửi tin nhắn này nọ, hay là làm màu cho cái lore, send scoreboard; thậm chí thêm quả animation nữa =)) việc này sẽ gây delay cho plugin rất nhiều.

II. CÁCH GIẢI QUYẾT
Ok mình đã giải thích vì sao cái hàm này nặng như vậy, và giờ là cách giải quyết: lưu trữ nó
Lưu trữ là sao? Vì cái chat color luôn luôn là tĩnh (*static effect*), bạn dùng nó bất cứ khi nào, với ai,.. đều ra 1 kết quả duy nhất nên tốt hơn là dùng nó 1 lần rồi lưu trữ!
Các bước như sau:
1. Đọc tệp tin cấu hình
2. Chuyển màu cho cấu hình
3. Sử dụng cấu hình đã dc chuyển màu
yay chỉ cần 3 bước thôi :))
Như vậy từ lần sau sử dụng là nó đã có màu rồi :v
Mặc dù nó làm lag một chút khi load server nhưng sẽ rất mượt khi chơi.

Ok Code sẵn hàm để sử dụng:
Java:
public static <T extends ConfigurationSection> T formatConfig(T section){
    Set<String> keys = section.getKeys(true);
    for(String key : keys){
        Object value = section.get(key);
        if(value instanceof String)
            section.set(key, ChatColor.translateAlternateColorCodes('&', (String) value));
    }
    return section;
}

Ví dụ cụ thể
Java:
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;

import java.io.File;
import java.util.Set;

public class Example {
    public static <T extends ConfigurationSection> T formatConfig(T section){
        Set<String> keys = section.getKeys(true);
        for(String key : keys){
            Object value = section.get(key);
            if(value instanceof String)
                section.set(key, ChatColor.translateAlternateColorCodes('&', (String) value));
        }
        return section;
    }

    private static FileConfiguration config;

    public static void loadConfig(){
        config = formatConfig(YamlConfiguration.loadConfiguration(new File("config.yml")));
    }

    public static FileConfiguration getConfig() {
        return config;
    }
}
Với ví dụ trên chỉ cần gọi loadConfig() từ hàm onEnabled() là được, sau này thì dùng getConfig() :v

Ok hết rồi nhé ae :v

-------------------------------------------------------------------------------
Thấy seri này hay? Hãy like cho mình để có thêm động lực viết tiếp.
Vui lòng ghi nguồn khi copy!
 
  • Like
Reactions: Ken
Tại sao lại không sử dụng String#replace nhỉ?
 
Dạo này rảnh + thấy forum thiếu content quá nên mình sẽ làm một seri hưỡng dẫn các bạn cải thiện code + hiệu suất plugin (Clean code & Performance optimization) dựa trên *my experience*
Chú ý: Seri này dành cho Bukkit (CCPO | Bukkit), với Java mình sẽ làm seri khác sau.



Yay bắt đầu vào topic với vấn đề đầu tiên :D: Lưu trữ ChatColor#translateAlternateColorCodes để sử dụng lại
ChatColor#translateAlternateColorCodes là cái hàm mà các bạn hay dùng để chuyển kí tự & thành kí tự § để khi nhìn vào Minecraft sẽ hiện màu.

I. VẤN ĐỀ
Vấn đề ở đây là cái hàm này khá "nặng", thử decompile ra sẽ như thế này:
Java:
    public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
    {
        char[] b = textToTranslate.toCharArray();
        for ( int i = 0; i < b.length - 1; i++ )
        {
            if ( b[i] == altColorChar && ALL_CODES.indexOf( b[i + 1] ) > -1 )
            {
                b[i] = ChatColor.COLOR_CHAR;
                b[i + 1] = Character.toLowerCase( b[i + 1] );
            }
        }
        return new String( b );
    }

Thấy hàm for không =)) Chỉ cần có một cái for thôi là đã nặng rồi. Vì sao ư? Nếu bạn có một chuỗi N kí tự thì code trên sẽ lặp qua N-1 kí tự, như vậy thời gian chạy là ~O(N-1)
Nhìn tiếp cái điều kiện if bên dưới, bạn sẽ thấy cái hàm String#indexOf (hàm dùng để trả về vị trí của chuỗi con trong chuỗi cha). Vậy sẽ có tối đa N-1 lần sử dụng cái indexOf đó.
ALL_CODES là một chuỗi chứa các mã màu (a,b,c,0,1,..) tổng cộng 34 kí tự (Ok ghi nhớ vì chúng ta cần nó sau này)

Thử dùng IDE để xem cái hàm đó bắt nguồn từ đâu..
Java:
public int indexOf(int ch) {
    return indexOf(ch, 0);
}

public int indexOf(int ch, int fromIndex) {
    return isLatin1() ? StringLatin1.indexOf(value, ch, fromIndex)
                      : StringUTF16.indexOf(value, ch, fromIndex);
}

Đây là code trong lớp String. Cái indexOf ở trên sẽ gọi method đầu trước rồi gọi tiếp method thứ hai với cái biến fromIndex luôn luôn là 0. Riêng cái biến value là để chứa chuỗi ở dạng byte array. Như vậy, value = 34.
Nhìn cái condition không có gì đặc biệt nên có thể bỏ qua, giờ so sánh giữa StringLatin1 và StringUTF16, vì cái string hay có tiếng Việt (ý mik` là UTF8) thế nên String chắc chắn sẽ gọi StringUTF16.
Check tiếp sẽ ra cái này.
Java:
    public static int indexOf(byte[] value, int ch, int fromIndex) {
        int max = value.length >> 1;
        if (fromIndex < 0) {
            fromIndex = 0;
        } else if (fromIndex >= max) {
            return -1;
        }

        if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
            return indexOfChar(value, ch, fromIndex, max);
        } else {
            return indexOfSupplementary(value, ch, fromIndex, max);
        }
    }

Chú ý cái max có toán tử bitwise dịch sang phải, vì value.length luôn trả về integer (chữ số) nên khúc ">> 1" tương tự với lấy một số chia cho 2 => max = 34/2 = 17
Ok nhìn tiếp xuống dưới sẽ thấy 2 hàm dc gọi, tiện cho ae xem luôn :D

Java:
    private static int indexOfChar(byte[] value, int ch, int fromIndex, int max) {
        checkBoundsBeginEnd(fromIndex, max, value);
        return indexOfCharUnsafe(value, ch, fromIndex, max);
    }

    private static int indexOfCharUnsafe(byte[] value, int ch, int fromIndex, int max) {
        for (int i = fromIndex; i < max; i++) {
            if (getChar(value, i) == ch) {
                return i;
            }
        }
        return -1;
    }



Java:
    private static int indexOfSupplementary(byte[] value, int ch, int fromIndex, int max) {
        if (Character.isValidCodePoint(ch)) {
            final char hi = Character.highSurrogate(ch);
            final char lo = Character.lowSurrogate(ch);
            checkBoundsBeginEnd(fromIndex, max, value);
            for (int i = fromIndex; i < max - 1; i++) {
                if (getChar(value, i) == hi && getChar(value, i + 1 ) == lo) {
                    return i;
                }
            }
        }
        return -1;
    }

Mặc dù nhìn 2 cái for các bạn thấy nó sẽ trả về ngay khi tìm thấy nhưng mà nó *éo có ý nghĩa gì cả vì giả sử kí tự cần tìm ở cuối cái byte array thì cái for kia cũng phải lặp 16-17 lần, tóm lại là rất mất thời gian.
Số lần tối đa của for luôn là 17). Mỗi lần sử dụng cái hàm String#indexOf thì tối đa bị gọi là N-1 lần. Như vậy là tối đa sẽ lặp tới 17*(N-1) lần.

Bây giờ thử tăng một chút chính xác, giả sử format luôn là dấu & + kí tự màu, vd &a, &b, &c => Số lần indexOf được gọi là X (X <= N/2) => Số lần lặp tối đa cho cả chuỗi là 17X.
Giả sử chuỗi cần chuyển màu là "&a&b&c" có 6 kí tự thì sẽ có tối đa 85 lần lặp. Nếu tăng độ chính xác thì sẽ có 3 kí tự & => 51 lần lặp.

Và để kiểm tra xem có đúng như dự đoán không, mình có làm cái test này:

Java:
import net.md_5.bungee.api.ChatColor;
import org.junit.Test;

public class translateAlternateColorCode {
    public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRr";

    static int counter = 0;
    static final int HI_BYTE_SHIFT;
    static final int LO_BYTE_SHIFT;
    static {
        HI_BYTE_SHIFT = 0;
        LO_BYTE_SHIFT = 8;
    }

    static char getChar(byte[] val, int index) {
        index <<= 1;
        return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
                ((val[index]   & 0xff) << LO_BYTE_SHIFT));
    }


    private static int indexOfChar(byte[] value, int ch, int fromIndex, int max) {
        for (int i = fromIndex; i < max; i++) {
            System.out.println(++counter);
            if (getChar(value, i) == ch) {
                return i;
            }
        }
        return -1;
    }

    private static int indexOfSupplementary(byte[] value, int ch, int fromIndex, int max) {
        if (Character.isValidCodePoint(ch)) {
            final char hi = Character.highSurrogate(ch);
            final char lo = Character.lowSurrogate(ch);
            for (int i = fromIndex; i < max - 1; i++) {
                if (getChar(value, i) == hi && getChar(value, i + 1 ) == lo) {
                    return i;
                }
            }
        }
        return -1;
    }

    public static int indexOf(byte[] value, int ch, int fromIndex) {
        int max = value.length >> 1;
        if (fromIndex < 0) {
            fromIndex = 0;
        } else if (fromIndex >= max) {
            // Note: fromIndex might be near -1>>>1.
            return -1;
        }
        if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
            // handle most cases here (ch is a BMP code point or a
            // negative value (invalid code point))
            return indexOfChar(value, ch, fromIndex, max);
        } else {
            return indexOfSupplementary(value, ch, fromIndex, max);
        }
    }


    public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
    {
        char[] b = textToTranslate.toCharArray();
        for ( int i = 0; i < b.length - 1; i++ )
        {
            if ( b[i] == altColorChar && indexOf(ALL_CODES.getBytes(), b[i + 1], 0) > -1 )
            {
                b[i] = ChatColor.COLOR_CHAR;
                b[i + 1] = Character.toLowerCase( b[i + 1] );
            }
        }
        return new String( b );
    }

    @Test
    public void test(){
        System.out.println(translateAlternateColorCodes('&', "&a&b&c"));
    }
}

Ok và đây là kết quả khi chạy
PNkYtzQ.png

Đúng 51 nhé =))

Giả sử giảm độ chính xác đi (X = N-1) thì ra kết quả như sau:
ggtuLci.png

tadaaaaa đúng nữa r =))

Ok vậy rút ra kết luận: Khi gọi hàm ChatColor#translateAlternateColorCodes với một chuỗi dài N kí tự sẽ có tối đa 17*N lần.
Và tưởng tượng đi, bạn luôn phải gửi tin nhắn này nọ trong plugin, hay là làm màu cho cái lore, send scoreboard, việc này sẽ gây chậm cho plugin rất nhiều.

II. CÁCH GIẢI QUYẾT
Ok mình đã giải thích vì sao cái hàm này nặng như vậy, và giờ là cách giải quyết: lưu trữ nó
Lưu trữ là sao? Vì cái chat color luôn luôn là tĩnh (*static effect*), bạn dùng nó bất cứ khi nào, với ai,.. đều ra 1 kết quả duy nhất nên tốt hơn là dùng nó 1 lần rồi lưu trữ!
Các bước như sau:
1. Đọc tệp tin cấu hình
2. Chuyển màu cho cấu hình
3. Sử dụng cấu hình đã dc chuyển màu
yay chỉ cần 3 bước thôi :))
Như vậy từ lần sau sử dụng là nó đã có màu rồi :v
Mặc dù nó làm lag một chút khi load server nhưng sẽ rất mượt khi chơi.

Ok Code sẵn hàm để sử dụng:
Java:
public static <T extends ConfigurationSection> T formatConfig(T section){
    Set<String> keys = section.getKeys(true);
    for(String key : keys){
        Object value = section.get(key);
        if(value instanceof String)
            section.set(key, ChatColor.translateAlternateColorCodes('&', (String) value));
    }
    return section;
}

Ví dụ cụ thể
Java:
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;

import java.io.File;
import java.util.Set;

public class Example {
    public static <T extends ConfigurationSection> T formatConfig(T section){
        Set<String> keys = section.getKeys(true);
        for(String key : keys){
            Object value = section.get(key);
            if(value instanceof String)
                section.set(key, ChatColor.translateAlternateColorCodes('&', (String) value));
        }
        return section;
    }
 
    private static FileConfiguration config;
 
    public static void loadConfig(){
        config = formatConfig(YamlConfiguration.loadConfiguration(new File("config.yml")));
    }

    public static FileConfiguration getConfig() {
        return config;
    }
}
Với ví dụ trên chỉ cần gọi loadConfig() từ hàm onEnabled() là được, sau này thì dùng getConfig() :v

Ok hết rồi nhé ae :v

-------------------------------------------------------------------------------
Thấy seri này hay? Hãy like cho mình để có thêm động lực viết tiếp.
Vui lòng ghi nguồn khi copy!
CC ! Hay vãi nồi !
 
đối với t thì toàn ép mấy ông config dùng Alt + 0137 thôi lul
 
cái này nó có tối ưu hơn cái có sẵn của bukkit ko nhỉ
Java:
public static String translateAlternateColorCodes(char alt, String text){
    return text.replaceAll("(?ium)("+alt+")([0-9a-fk-or])", "\u00A7$2")
}
 
cái này nó có tối ưu hơn cái có sẵn của bukkit ko nhỉ
Java:
public static String translateAlternateColorCodes(char alt, String text){
    return text.replaceAll("(?ium)("+alt+")([0-9a-fk-or])", "\u00A7$2")
}
còn nặng hơn :3 (do ba cái regex nó phải tìm kiếm rồi thay thế rất là lòng vòng :v)
 
Sao ko dùng
public static String color(String string)
Ròi trả về chatcolor.translatecolorcode ôg nhỉ
 
Sao ko dùng
public static String color(String string)
Ròi trả về chatcolor.translatecolorcode ôg nhỉ
dùng v là giống vs gọi thẳng chatcolor#translatecolorcode() luôn r
í bài viết này là nếu load từ config thì nên xử lí cái vụ color trước, để lúc dùng k phải lm nữa
 
Bình thường là chúng ta làm như thế này
20986


Còn theo bài viết là như thế này
20987

chỉ cần 1 lần dùng translateAlternateColorCodes khi load là dc
 
Đã lỡ tối ưu rồi thì về mặc định khi load file yaml đã load vô ram rồi, chỉ khi nào xài thì create vứt vô cache rồi reuse <(") chưa chắc mọi thứ trong YAML đều được sử dụng

21346
 
Similar content Most view Xem thêm
Back
Top Bottom