背景:网路传输字节数组,字节数组转成字符串后传输(因为用了thrift,不支持C++字节数组)。
问题:字节数组包含取值为0,字符串会把0当成结束符而出错。Base64编解码暂不考虑,原因是性能不满足,因为字节数组最大可达到100Mc++Java字符串字节数组
问题:字节数组包含取值为0,字符串会把0当成结束符而出错。Base64编解码暂不考虑,原因是性能不满足,因为字节数组最大可达到100Mc++Java字符串字节数组
回复2楼:主要是对性能有影响,编解码总共耗时在3.6-4s之间,系统存在大量这个消息发送,对性能影响非常大。
1、算法通用,并且直接支持位运算实现,性能未必不比转义差,当然确实消耗空间;
2、定长,便于做定位检索,也就是说你如果想定位原始字符第 N 个,可以通过计算得出对应的BASE64串应该是哪个;转义是变长的,无法做定位检索。Anyway,最后仍然看你自己的应用场景了,如果程序本身就是流式处理,转义倒也没啥问题。
0 -> "*0"
'*' -> "*1"
'#' -> "*2"
其他 -> "#其他"
同时考虑压缩,如果连续使用同一个转义字符,则只保留第一个,后面的删除,例如原数组为0*#ABC,编码后为"*012#ABC",性能为2.7-3s(原为3.6-4s),提升了20%,但是还是不满足要求,请大家看看哪里还能优化,代码如下。另外,冗余的代码主要考虑性能。
{
int length = buff.limit();
if (length <= 0)
{
return null;
}
StringBuffer result = new StringBuffer(length*2);
byte[] a = buff.array();
byte oldFlag = 2, newFlag = 2;
switch (a[0])
{
case 0:
oldFlag = 0;
result.append("*0");
break;
case 42: //'*'
oldFlag = 0;
result.append("*1");
break;
case 35: //'#'
oldFlag = 0;
result.append("*2");
break;
default:
oldFlag = 1;
result.append('*');
result.append((char)a[0]);
break;
}
for (int i=1; i<length; i++)
{
switch (a[i])
{
case 0:
newFlag = 0;
if (newFlag != oldFlag)
{
result.append("*0");
oldFlag = newFlag;
}
else
{
result.append('0');
}
break;
case 42: //'*'
newFlag = 0;
if (newFlag != oldFlag)
{
result.append("*1");
oldFlag = newFlag;
}
else
{
result.append('1');
}
break;
case 35: //'#'
newFlag = 0;
if (newFlag != oldFlag)
{
result.append("*2");
oldFlag = newFlag;
}
else
{
result.append('2');
}
break;
default:
newFlag = 1;
if (newFlag != oldFlag)
{
result.append("#");
oldFlag = newFlag;
}
result.append((char)a[i]);
break;
}
}
return result.toString();
}
public static ByteBuffer decode(String str)
{
int length = str.length();
if (length <= 0)
{
return null;
}
ByteBuffer buff = ByteBuffer.allocate(length);
char a;
byte flag = 2;
for (int i=0; i<length; i++)
{
a = str.charAt(i);
switch(a)
{
case '*':
flag = 0;
break;
case '#':
flag = 1;
break;
case '0':
switch (flag)
{
case 0:
buff.put((byte)0);
break;
case 1:
buff.put((byte)a);
break;
default:
break;
}
break;
case '1':
switch (flag)
{
case 0:
buff.put((byte)42); //'*'
break;
case 1:
buff.put((byte)a);
break;
default:
break;
}
break;
case '2':
switch (flag)
{
case 0:
buff.put((byte)35); //'#'
break;
case 1:
buff.put((byte)a);
break;
default:
break;
}
break;
default:
buff.put((byte)a);
break;
}
}
buff.flip();
return buff;
}
首先,转义字符基本上一定是优先选用出现概率最低的,因此连续出现转义字符是没啥道理,只能说明转义字符选的不合适;
其次,转义字符用得越少越好,要知道大量case判定也消耗CPU。
StringBuffer result = new StringBuffer(length*2);
result.append(); ByteBuffer buff = ByteBuffer.allocate(length);
buff.put();尤其是result.append() 现在对其进行了修改,使用数组存储,直接申请数组空间,不再申请StringBuffer,性能提高非常大,使用base64,编解码总共耗时0.5s左右。(说明下:测试机器性能比较高,2个CPU,每个CPU 8个核,128G内存,但是JVM只用了1G)
public static byte[] encode(byte[] data) {
byte[] results = new byte[data.length * 2]; // 夸张点的进行空间预申请了
int p = 0;
for (int i = 0; i < data.length; i++) {
if (data[i] == 0) {
results[p++] = (byte) '#';
results[p++] = (byte) '0';
} else if (data[i] == '#') {
results[p++] = (byte) '#';
results[p++] = (byte) '1';
} else results[p++] = data[i];
}
return results;
}