在使用TCP協(xié)議作為傳輸協(xié)議時(shí)笨忌,很多時(shí)候都需要輸出原始報(bào)文用來查看傳輸數(shù)據(jù)是否正確冬殃。特別是在物聯(lián)網(wǎng)中的硬件服務(wù)器魂角,上下行的數(shù)據(jù)量非常大,任何關(guān)于報(bào)文的處理的開銷都會(huì)因?yàn)橄⒌脑黾佣杀斗糯蟆?/p>
不管使用ByteArray或者ByteBuffer當(dāng)做數(shù)據(jù)容器赡茸,輸出日志時(shí)夭禽,都需要進(jìn)行兩步城菊,第一步:把字節(jié)數(shù)組的字節(jié)轉(zhuǎn)成字符;第二步:在拼接字符形成字符串。
一般的處理方式
迭代拼接字符串
創(chuàng)建StringBuilder顶考,迭代ByteArray赁还,格式化每個(gè)byte后,追加到StringBuilder中驹沿,最后使用StringBuilder.toString()方法輸出完整字符串艘策。
/**
* 根據(jù)字節(jié)數(shù)組,輸出對(duì)應(yīng)的格式化字符串
* @param bytes 字節(jié)數(shù)組
* @return 字節(jié)數(shù)組字符串
*/
public static String printBytesByStringBuilder(byte[] bytes){
StringBuilder stringBuilder = new StringBuilder();
for (byte aByte : bytes) {
stringBuilder.append(byte2String(aByte));
}
return stringBuilder.toString();
}
public static String byte2String(byte b){
return String.format("%02x ",b);
}
特定優(yōu)化處理
預(yù)分配字符數(shù)組渊季,使用常量池來實(shí)現(xiàn)字節(jié)到字符的轉(zhuǎn)換朋蔫。填充提前預(yù)分配好的字符數(shù)組。輸出字符串却汉!
第一步:窮舉所有byte對(duì)應(yīng)的字符數(shù)組
第二步:使用索引的方式進(jìn)行字符串拼接
/**
* 使用字符數(shù)組進(jìn)行byte字節(jié)信息的輸出 如果默認(rèn)進(jìn)制標(biāo)識(shí)的話驯妄,不打印'0x',一個(gè)byte只需要兩個(gè)char 例如: 0x9 = '0' + '9' 0xAF = 'A' + 'F'
* 0x9 0xAF = > '0'+'9'+' '+'A'+'F'
* <p>
* 一個(gè)byte需要2個(gè)字符標(biāo)識(shí),外加一個(gè)空格字符
*
* @param bytes 需要格式化的byte
* @return 字節(jié)數(shù)組 字符串
*/
public static String printBytesByCharPool(byte[] bytes) {
int byteLength = bytes.length;
int charLength = byteLength * 3;
char[] content = new char[charLength];
int unsignedByte = 0;
int startIndex = 0;
for (int i = 0; i < byteLength; i++) {
// 使byte變?yōu)闊o符號(hào)的byte
// b2u
unsignedByte = ((int) bytes[i]) & 0xFF;
char[] chars = BYTE_CHARS[unsignedByte];
startIndex = i * 3;
// byte的第一位字符
content[startIndex] = chars[0];
// byte格式化的第二位字符
content[startIndex + 1] = chars[1];
// 尾隨的空格字符
content[startIndex + 2] = WHITE_CHAR;
}
return new String(content);
}
分別使用1024長(zhǎng)度的byteArray進(jìn)行循環(huán)1200次打印結(jié)果:
# 使用stringBuilder
printBytesByStringBuilder useTime=1070
# 使用字符池
printBytesByCharPool useTime=4
可以看出來速度差別在兩個(gè)數(shù)量級(jí)
其他開銷分析
分析這兩種方法存在的性能開銷點(diǎn)有:String.format的性能合砂,StringBuilder的性能
String.format
String.format方法用來格式化代碼青扔,進(jìn)入源碼可以看到,每次都會(huì)創(chuàng)建一個(gè)模板對(duì)象翩伪,不能復(fù)用微猖,這個(gè)必然存在一定的性能開銷
public static String format(String format, Object... args) {
return new Formatter().format(format, args).toString();
}
StringBuilder的性能
雖然StringBuilder 已經(jīng)是最常用的字符串拼接方法,如果頻繁調(diào)用也會(huì)發(fā)現(xiàn)內(nèi)部其實(shí)也有動(dòng)態(tài)擴(kuò)容的機(jī)制缘屹,類似于hasmap的擴(kuò)容凛剥。當(dāng)容量沒有預(yù)估時(shí),會(huì)發(fā)生多次動(dòng)態(tài)擴(kuò)容轻姿。
使用場(chǎng)景
在用于做Tcp服務(wù)器時(shí)犁珠,客戶端與服務(wù)端使用Tcp長(zhǎng)連接進(jìn)行通訊,需要輸出原始報(bào)文進(jìn)行信息查看和追蹤互亮,這個(gè)時(shí)候輸出報(bào)文的開銷就伴隨這設(shè)備增多會(huì)成倍放大盲憎,關(guān)于輸出Tcp中報(bào)文字節(jié)的開銷就不能小視。
如果是次場(chǎng)景優(yōu)化胳挎,其他可以采取的方法有:
- 盡量使用定長(zhǎng)協(xié)議,可以提前分割定長(zhǎng)協(xié)議的格式化所需要內(nèi)存空間
- 如果在輸出字節(jié)數(shù)組時(shí)溺森,需要增加前綴或者后綴慕爬,也可以在申請(qǐng)字節(jié)數(shù)組對(duì)應(yīng)的字符數(shù)組時(shí)提前把前后綴的內(nèi)容對(duì)應(yīng)的空間一起創(chuàng)建出來
- 如果消息量特別大時(shí)也可以考慮使用隊(duì)列進(jìn)行延遲或異步打印,不影響網(wǎng)絡(luò)吞吐