一、编码介绍
字符编码是一种将字符映射到数字代码的规则或方式。在计算机中,所有的数据最终都以二进制形式存储,包括文本数据。因此,要在计算机中存储和处理文本,就需要将字符转换为对应的数字编码。
字符编码可以分为两种基本类型:固定长度编码和可变长度编码。
-
固定长度编码:每个字符都被映射到固定长度的二进制序列。ASCII(美国信息交换标准码)就是最著名的固定长度编码,它使用7位二进制表示128个字符,包括26个拉丁字母、阿拉伯数字、标点符号等。
-
可变长度编码:不同的字符可能被映射到不同长度的二进制序列。Unicode是一种常用的可变长度编码,它支持几乎所有世界上使用的字符,包括各种语言的字母、符号、表情符号等。而UTF-8、UTF-16和UTF-32则是Unicode的实现方式之一。
下面是一些常见的字符编码:
-
ASCII(American Standard Code for Information Interchange):一种7位固定长度编码,用于表示英文字母、数字和一些常用符号,共128个字符。
以下是ASCII码表的部分内容:
ASCII码 字符 描述 --------------------------------- 0 NUL 空字符 1 SOH 标题开始 2 STX 文本开始 3 ETX 文本结束 4 EOT 传输结束 5 ENQ 请求 6 ACK 确认 7 BEL 响铃 8 BS 退格 9 HT 水平制表符 10 LF 换行 11 VT 垂直制表符 12 FF 换页 13 CR 回车 14 SO 选择 15 SI 取消选择 16 DLE 数据链路转义 17 DC1 设备控制1 18 DC2 设备控制2 19 DC3 设备控制3 20 DC4 设备控制4 21 NAK 拒绝接收 22 SYN 同步 23 ETB 块结束 24 CAN 取消 25 EM 结束媒体 26 SUB 替换 27 ESC 转义 28 FS 文件分隔符 29 GS 组分隔符 30 RS 记录分隔符 31 US 单元分隔符 32 (space) 空格 33 ! 感叹号 34 " 双引号 35 # 井号 36 $ 美元符号 37 % 百分号 38 & 和号 39 ' 单引号 40 ( 左括号 41 ) 右括号 42 * 星号 43 + 加号 44 , 逗号 45 - 减号/破折号 46 . 句号 47 / 斜杠 48-57 0-9 数字 58 : 冒号 59 ; 分号 60 < 小于号 61 = 等号 62 > 大于号 63 ? 问号 64 @ at符号 65-90 A-Z 大写字母 91 [ 左方括号92 \ 反斜杠93 ] 右方括号 94 ^ 上箭头 95 _ 下划线 96 ` 反引号 97-122 a-z 小写字母 123 { 左花括号 124 | 竖线 125 } 右花括号 126 ~ 波浪符 127 DEL 删除
-
ISO 8859-1:也称为Latin-1,是一种8位固定长度编码,扩展了ASCII,包含了西欧常用的字符。
-
UTF-8(Unicode Transformation Format - 8-bit):一种可变长度编码,使用1至4个字节来表示Unicode字符,是互联网上使用最广泛的字符编码方式,支持全球范围内的所有字符。
-
UTF-16:一种可变长度编码,使用2或4个字节来表示Unicode字符,常用于Java和Windows平台。
-
UTF-32:一种固定长度编码,使用4个字节来表示Unicode字符,适合在内存中处理Unicode文本。
选择合适的字符编码取决于应用的需求和环境,但通常推荐使用Unicode的变种,如UTF-8,因为它能够支持全球范围内的所有字符,并且兼容ASCII。在Java中,String对象使用的是UTF-16编码,但在文件读写、网络通信等场景中,需要注意字符编码的设置以避免出现乱码问题。
二、中文乱码浅析与问题解决
-
字符编码不匹配:Java中的String对象是以UTF-16编码的,而很多时候中文字符在输入输出时可能会涉及到其他编码格式,比如UTF-8、GBK等。当编码格式不一致时就可能导致中文乱码。
- 解决方法:确保在输入、输出以及处理中使用一致的字符编码。一般情况下,推荐使用UTF-8编码,因为它支持最广泛的字符范围。
-
文件读写编码设置错误:在读取或写入文件时,如果没有指定正确的字符编码,就会导致中文乱码。
-
解决方法:在使用InputStreamReader或OutputStreamWriter时,明确指定编码格式,例如:
InputStreamReader isr = new InputStreamReader(new FileInputStream("file.txt"), "UTF-8"); OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("file.txt"), "UTF-8");
-
-
网络传输编码设置错误:在进行网络通信时,如果没有指定正确的字符编码,同样会导致中文乱码。
-
解决方法:在使用Socket通信或者HttpURLConnection时,设置字符编码,例如:
Socket socket = new Socket("hostname", port); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
-
-
IDE或控制台环境中的编码设置错误:有时候中文乱码是由于IDE或控制台的编码设置不正确造成的
-
字符编码转换不正确:在进行字符编码转换时,如果使用了不正确的方式,也可能导致中文乱码
三、解决中文乱码的通解
通解很简单,打印!打印!!打印!!把所以出现乱码位置的地方,用十六进制的形式的形式打印出来,然后根据码表进行对照,看看问题到底出在哪里。笔者用以下UDP服务器的例子具体说明:
笔者这里写了一个回响服务器:
public class UdpEchoServer {
DatagramSocket socketServe = null;
public UdpEchoServer(int port) throws SocketException {
socketServe = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while (true) {
//接受客户端request
DatagramPacket request = new DatagramPacket(new byte[4096], 4096);
socketServe.receive(request);
//根据输入包,处理
String strRequest = new String(request.getData(), 0, request.getLength());//有效长度
String strResponse = process(strRequest);
//构造response数据包
//注意这里的长度,尤其是中文的字节长度与String中的长度不一样
DatagramPacket response = new DatagramPacket(strResponse.getBytes(),0,strResponse.getBytes().length);
//state test
for (byte cur :
response.getData()) {
System.out.printf("%x",cur);
}
//end
response.setAddress(request.getAddress());
response.setPort(request.getPort());
socketServe.send(response);
//打印日志
System.out.printf("[%s:%d]request:%s,response:%s",request.getAddress(),request.getPort(),strRequest,strResponse);
}
}
public String process(String request) {
//回响服务器
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
udpEchoServer.start();
}
}
这时,出现了英文可以正确显示,但中文不行
但这时服务器的日志却正确构造出了响应
于是,我就把response中的数据全部打印出来,与真正的编码进行对比(这里你首先要知道Java String 默认格式是utf8)
for (byte cur :
response.getData()) {
System.out.printf("%x",cur);
}
发现结果不一样,原因就是,构造包时长度不够,经过排查发现报的长度应该是
DatagramPacket response = new DatagramPacket(strResponse.getBytes(),0,strResponse.getBytes().length);
而不是
DatagramPacket response = new DatagramPacket(strResponse.getBytes(),0,strResponse.length());
经过打印,问题解决,所以解决中文乱码的最佳方式就是把文字以十六进制的形式打印出来,手动比较,发现问题。