论坛首页 Java企业应用论坛

《碰到的一个编码问题》的回答:UTF-8的汉字字节!

浏览 21034 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-08-17  
埋伏:
1、UTF-8用几个字节表示一个汉字?
这各答案你可能了解,但也可能不了解,我敢打保票一半人会不清楚(包括特意查资料之前的我)。
了解这个对编程有什么影响?


以下我把对yoolywu的回答,转为帖子发表,以表重视。


yollywu的问:
引用
系统有两个子系统,一个是BS的,一个是delphi做的CS,中间的数据传输是通过XML进行传输的。在XML传输的功能实现后,要求对XML进行加密解密.加密解密算法是CS端用delphi写的,然后这边用JAVA写个同样的算法。现在碰到的一个问题是:
用该算法的时候,CS和BS各自都能够加解密,我这边的过程是这样的。。。。[但最后]中文始终是乱码
  
        StringBuffer strbuf = new StringBuffer();
	try {
		FileInputStream in = new FileInputStream(file);
		int size = 0;
		byte [] buf = new byte[1024];	
		while ((size=in.read(buf)) != -1) {
			strbuf.append(new String(buf,0,size));
		}
		
	} catch (FileNotFoundException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (IOException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	}
       return strbuf;
          





Qieqie的答:



以下的代码是错误的:
StringBuffer strbuf = new StringBuffer();   
...
 strbuf.append(new String(buf,0,size));  


第一、
你应该使用ByteArrayOutputStream,将InputStream的字节全部读出来,然后转成byte[]数组,最后在根据你和对方协议规定的字符集合(假设你们规定的是UTF-8,如果没有规定,那么就补充上吧),将byte[]变成String: String theString = new String(bytes, "UTF-8")。
不加"UTF-8"的new String,将使用Java环境设置的字符集,没有特别设置的情况下也就是操作系统的字符集。这是不可靠的。

第二、
不能使用byte[]+StringBuffer:StringBuffer是针对char操作的(String也是)。读取byte时可能刚好把一个多字节的char分成前后两批加入StringBuffer。这样就破坏了char的完整性了。而如果你使用UTF-8编码的中文,你就会中招,导致乱码(其实是因为你的读取是由于byte失去原有顺序导致的,跟一般的乱码还不一样)
--
在UTF-8编码集中,每个汉字使用 3个字符表示! 实践证明:
1、创建一个UTF-8编码的文件:weare.txt
2、写入三个字:“我们是
3、运行以下代码:
public class UTF8 {

	public static void main(String[] args) throws IOException {
		String p = "weare.txt";
		InputStream in = new FileInputStream(p);
		int read = in.read(new byte[1204]);
		System.out.println(read);
		
	}
}

4、你会发现打印出来的是 9 !

所以,byte[]+StringBuffer的使用方式是错误的!

不过可以使用StringBuffer + bufferedReader.readLine(),读出一行行后再加入StringBuffer。
或者第2楼说的stringbuffer+reader.read(char[])的形式(毕竟错误是由于byte[]导致的,而非StringBuffer)




参考资料:

zh.wikipedia.org 写道

UTF-8 使用一至四个字节为每个字符编码。128 个 ASCII 字符(Unicode 范围由 U+0000 至 U+007F)只需一个字节,带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及马尔代夫语(Unicode 范围由 U+0080 至 U+07FF)需要二个字节,其他基本多文种平面(BMP)中的字符(CJK属于此类-Qieqie注)使用三个字节,其他 Unicode 辅助平面的字符使用四字节编码。


   发表时间:2007-08-17  
对于这个例子,一个简单办法就是用InputStreamReader:
InputStreamReader in = new InputStreamReader(new FileInputStream(file), "UTF-8");
char[] buf ...
读取in到buf,再append到strbuf
0 请登录后投票
   发表时间:2007-08-17  
如果是按字节来分批读取,不光是utf-8编码,gbk也是会有问题的,两个汉字是4个字节,而一个英文字母加一个汉字是3个字节,所以不可能每次都刚好从两个字符之间切开,除非所有字符都是汉字,可以按双数来取。
所以必须把字节流全部加载进来,整体一起转换成字符串。

如果事先知道字符编码,那么可以用楼上的简单方法,按字符来读取,读出来的都是完整的字符。

如果事先不知道编码格式,但是像xml文件头中注明了编码格式,那么可以按iso-8859-1的编码格式将读进来的字节流进行字符化,然后根据读到的encoding得到编码格式,按照正确的编码格式重新将读到的内容整体转换成正确的字符串。
因为iso-8859-1格式一个字节对应一个字符,并且每一个字节值都有一个对应的字符,将字符还原到字节时原来的值不会发生改变,所以按字节分批读取也不会破坏数据的完整性。并且gbk和utf-8都是兼容iso-8859-1的,即基本字符的编码是一样的,所以按iso-8859-1编码得到的encoding那一段字符是正确的,可识别的。
也可以只读取一个头部,找到正确的编码格式后,再用InputStreamReader按字符读取。
0 请登录后投票
   发表时间:2007-08-18  
http://www.regexlab.com/zh/encoding.htm
0 请登录后投票
   发表时间:2007-08-18  
  想说的一个问题是,协议方过来的是加密后十六进制数的一个大的字符串。文件格式我打开的时候另存默认格式是ANSI.
0 请登录后投票
   发表时间:2007-08-18  
  我自己在读取密文的时候用的是这个
  
InputStreamReader read=null;
	 String line;
     try {
       read=new InputStreamReader (new FileInputStream(file),"UTF-8");
       BufferedReader fileBuffer=new BufferedReader(read);
		line=fileBuffer.readLine();
	    while (line != null){
	    	  strbuf.append(line);
	    	  strbuf.append("\n"); 
	          line = fileBuffer.readLine();
	          }
         } 
     catch (IOException e4){
         e4.printStackTrace();
      }
,按字节读是另外有个经验的人说的,但是我发现两种读法都没能改变结果,所以就没有换过来,我觉的主要问题还真有可能是算法上面,就是说我在debug的时候解密后的串转码UTF-8后,因为始终都有几个汉字不能正常显示。
0 请登录后投票
   发表时间:2007-08-18  
谢谢各位,忙了我几天,一直不敢确定是哪边的错误,刚才被BOSS级人物解决了,他两边都懂,分析结果是那边算法有一处小问题造成的。
0 请登录后投票
   发表时间:2007-08-18  
我还以为utf8中中文是3个字节是基本常识呢,没想到还是有人不清楚。。。faint
0 请登录后投票
   发表时间:2007-08-18  
LS的别ft,现实就是这样。

投入门贴的同学,认为这个知识简单,没有体认到“一个汉字用两个字节表示”的错误的结论几乎根深蒂固,因为根深蒂固,所以需要特别发帖指出。

(是否解决了yollywu的问题或者没解决可能不重要|因为也不是很了解他的问题。只是借yollywu的帖子作为引子而已,yollywu见谅)
0 请登录后投票
   发表时间:2007-08-18  
yollywu 写道
  想说的一个问题是,协议方过来的是加密后十六进制数的一个大的字符串。文件格式我打开的时候另存默认格式是ANSI.


绕了一圈。被你提醒这一说,问题就不是出在编码上。而是这样:
1、密文单位是字节而非字符,那用什么编码读取都可以,UTF-8/GBK/ISOxxxx都可以,他们是兼容ISOxxxx的
2、既然是字节而非字符,那byte[]+StringBuffer不是问题的错误所在,用它读没有问题(肯定是对方的问题,刚好你们的协调人帮你发现了问题确实如此)
3、但,把密文转为明文后,再读取要用预先指定的字符集读取(此时不能使用byte[]+StringBuffer)
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics