背景:网路传输字节数组,字节数组转成字符串后传输(因为用了thrift,不支持C++字节数组)。
问题:字节数组包含取值为0,字符串会把0当成结束符而出错。Base64编解码暂不考虑,原因是性能不满足,因为字节数组最大可达到100Mc++Java字符串字节数组

解决方案 »

  1.   

    Base64其实应该是不错的,100M也就变成大概130M大小。
      

  2.   

    回复1楼:遇到这个问题,我的第一也是替换掉或者转义,但是发现不可行啊,原因是0-255个取值都是会出现的,比如0转成$,而$转成$$,也就是$作为转义字符,但是0$转换之后是$$$,再转回来就是$0,和原始不附合,00->$$->$也是有问题的。
    回复2楼:主要是对性能有影响,编解码总共耗时在3.6-4s之间,系统存在大量这个消息发送,对性能影响非常大。
      

  3.   

    不过我仍然想吐槽下Base64,它的两大好处:
    1、算法通用,并且直接支持位运算实现,性能未必不比转义差,当然确实消耗空间;
    2、定长,便于做定位检索,也就是说你如果想定位原始字符第 N 个,可以通过计算得出对应的BASE64串应该是哪个;转义是变长的,无法做定位检索。Anyway,最后仍然看你自己的应用场景了,如果程序本身就是流式处理,转义倒也没啥问题。
      

  4.   

    多谢ldh911解答,设置多个转义字符确实可以实现,我先试下性能。不过空间消耗也是翻倍了。
      

  5.   

    测试了下,具体思路是:*#作为转义字符
    0 -> "*0"
    '*' -> "*1"
    '#' -> "*2"
    其他 -> "#其他"
    同时考虑压缩,如果连续使用同一个转义字符,则只保留第一个,后面的删除,例如原数组为0*#ABC,编码后为"*012#ABC",性能为2.7-3s(原为3.6-4s),提升了20%,但是还是不满足要求,请大家看看哪里还能优化,代码如下。另外,冗余的代码主要考虑性能。
      

  6.   

        public static String encode(ByteBuffer buff) 
        {
         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;
        }
        
      

  7.   

    从你代码来看,你并没有使用流式处理(也许是因为你的主体程序本来就无法支持该模式),基本上不会有太多性能上的优化空间了。你所设计的压缩,老实说我不认为有什么价值:
    首先,转义字符基本上一定是优先选用出现概率最低的,因此连续出现转义字符是没啥道理,只能说明转义字符选的不合适;
    其次,转义字符用得越少越好,要知道大量case判定也消耗CPU。
      

  8.   

    上面的代码,包括之前的代码,对性能的消耗主要在
    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)
      

  9.   

    Base64是支持流式处理的,而且是定长处理,所以性能高是很合理的。如果你把转义方式也改为流式处理,即便是变长,性能也会相当可以,我认为应该跟Base64不相伯仲。哪怕是先用数组来模拟流式处理也是可以的,参考伪代码如下:
    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;
    }