作者:曾巧(numenzq)

    最近在与银行做一些应用,由于特定环境的原因,只能用GB2312字符编码,大家都知道,在GB2312编码下,一个中文是占两个字节的,而在java中,字符的处理是用的Unicode编码,所以一个中文只占一个字节。这样的话,我想从String或StringBuffer中取出想要的中文字符就会有一些问题。下面举个例子来说明吧。

 

String str = "测试数据:正确读取String中指定长度的中文字符"; 
  
System.out.println("content: " + str); 
  
System.out.println("length: " + str.length()); 
  
System.out.println("substring(0, 6): " + str.substring(0, 6));

Console:

content: 测试数据:正确读取String中指定长度的中文字符

length: 25

substring(0, 6): 测试数据:正

在上面的测试中,我们不难发现取出来的实际值(测试数据:正)与预期值(测试数)不一样。这正是上面所提到的问题,我们需要计算出这个偏移量(offset),也就是说把string里的每一个中文都当两个字节来算,公式为:偏移量 = 总偏移量– 多出部分字符的偏移量。具体的代码如下:

private staticStringint length, 
  
    String charset) throws UnsupportedEncodingException { 
  
 
  
      
  
    // 实际取出来的字符串,还存在偏移 
  
    String tmpStr = data.substring(0, length); 
  
 
  
      
  
// 计算实际字符串的长度 
  
    int tmpLength = tmpStr.getBytes(charset).length; 
  
 
  
      
  
    // 如果实际长度是想要取的长度的两倍,说明实际取出的字符串全为中文字符,我的期望值正好为想要取的长度的一半 
  
    if (length * 2 == tmpLength) { 
  
       return tmpStr.substring(0, length / 2); 
  
    } 
  
 
  
      
  
    // 实际字符串比期望字符串多出的一部份字符串,存在偏移 
  
    String leftString = tmpStr.substring(length * 2 - tmpLength, length); 
  
 
  
      
  
    // 计算多出的字符串的长度 
  
    int fixOffset = leftString.getBytes(charset).length; 
  
 
  
      
  
    // 计算出我们向要的偏移量 
  
    int offset = tmpLength - length - (fixOffset - leftString.length()); 
  
 
  
      
  
    // 期望的字符串 
  
    return tmpStr.substring(0, length - offset); 
  
}

    如果你仔细分析上面的代码,或则拿实际的数据来测试的话,可能会有StringIndexOutOfBoundsException异常。还是举个例子来说明出现这个异常的原因吧。假使我们的测试数据是"多哈亚运会",如果是GB2312的编码,这五个字符占10位,而在String里只有五位,如果我们只想取前四个字符,应该是substring.(0, 8);这当然会出现上面提到的异常啦。为了解决这个问题,我们可以不用偏移量这个概念来取数据,而是用字节数组来完成这件事,具体代码如下:

private static String getStringWithByteArray(String data, int length, 
  
String charset) throws UnsupportedEncodingException { 
  
 
  
      
  
// 期望值 
  
    byte[] resultnew byte[length]; 
  
    // 实际内容 
  
    byte[] content = null; 
  
 
  
      
  
    // 如果实际值大于想要获得字符串的长度,则内容只取想要的长度相应的位数,否则取所有的内容 
  
    if (data.length() > length) { 
  
       content = data.substring(0, length).getBytes(charset); 
  
    } else { 
  
       content = data.toString().getBytes(charset); 
  
    } 
  
 
  
      
  
    // 如果字节数组的长度都小于想要的长度(数据不够取), 错误处理。 
  
    if (content.length < length) { 
  
       // TODO error 
  
       throw new IndexOutOfBoundsException(); 
  
    } 
  
 
  
      
  
    // 将要取的数据给期望值 
  
    System.arraycopy(content, 0, result, 0, length); 
  
 
  
      
  
    // 返回期望值 
  
    return newresult, charset); 
  
}

这样修改后,我们就可以放心的使用了,但是这个方法还不能通用,你从代码中也看到了,取子串时,都是从头开始取的,如果你想从特定位置开始取子串的话,只有多一个startPosition参数了,这个就需要你自己修改了:P;如果有问题或更好的想法,请与我联系。