首页 > 编程语言 >字符编码和Java中的乱码问题

字符编码和Java中的乱码问题

时间:2023-06-22 22:06:30浏览次数:47  
标签:编码 UTF 字节 字符 符号 乱码 Unicode Java


ASCII码

  在计算机内部,所有的信息最终都表示为一堆二进制形式的数据。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,称为一个字节(byte),从0000000到11111111。上世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系做了统一规定,称之为ASCII码(American Standard Code for Information Interchange)并沿用至今。ASCII码一共规定了128个字符的编码,比如空格是32(00100000),大写的字母A是65(01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。
  英语用128个符号编码就够了,但是其他语言很多都不止128个符号,比如在法语中,字母上方有注音符号,这种字符就无法用ASCII码表示。为此,在某些欧洲国家会利用字节中闲置的最高位编入新的符号,例如法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是这里又出现了新的问题,不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母ג,但是不管怎样,所有这些编码方式中,0~127表示的符号是一样的,不一样的只是128~255的这一段。
  至于亚洲国家的文字,使用的符号就更多了,1994年由中华书局、中国友谊出版公司出版的《中华字海》就收录了85568个汉字。一个字节只能表示256种符号肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码GB2312使用两个字节表示一个汉字,所以理论上最多可以表示65536个符号。

Unicode

  世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失,这就是Unicode。Unicode是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0041表示英语的大写字母A,U+660A表示汉字”昊”。需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。
比如,汉字”昊”的Unicode是十六进制数660A,转换成二进制数是0110 0110 0000 1010,也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节。这里就有两个问题,第一个问题是,如何才能区别Unicode和ASCII?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。为此出现了Unicode的多种存储方式,也就是说有多种不同的二进制格式可以用来表示Unicode。

UTF-8

  UTF-8就是在互联网上使用最广的一种Unicode的实现方式。其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用。UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8的编码规则很简单,只有二条:
1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的Unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2. 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的Unicode码。
下表总结了编码规则,字母x表示可用编码的位。

Unicode符号范围

UTF-8编码方式

0000 0000-0000 007F

0xxxxxxx

0000 0080-0000 07FF

110xxxxx 10xxxxxx

0000 0800-0000 FFFF

1110xxxx 10xxxxxx 10xxxxxx

0001 0000-0010 FFFF

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

说明:解读UTF-8编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

Java中的编解码

I/O操作时的编解码

  在进行I/O操作时经常会遇到将字节流转换成字符流的场景,Java的API提供了InputStreamReader和OutputStreamWriter来解决这样的问题,而这两个类的构造器中都可以指定编码/解码的方式。

InputStreamReader(InputStream in)  // 使用默认的字符集
InputStreamReader(InputStream in, String charsetName)  throws UnsupportedEncodingException
InputStreamReader(InputStream in, Charset cs)
InputStreamReader(InputStream in, CharsetDecoder dec)
OutputStreamWriter(OutputStream out)  // 使用默认的字符集
OutputStreamWriter(OutputStream out, String charsetName) throws UnsupportedEncodingException
OutputStreamWriter(OutputStream out, Charset cs)
OutputStreamWriter(OutputStream out, CharsetEncoder enc)

  从JDK 1.4引入了NIO开始,我们可以使用Charset类提供encode和decode方法实现字符数组和字节数组的转换,代码如下所示:

Charset cs = Charset.forName("utf-8");
String str = "骆昊";
ByteBuffer buffer1 = cs.encode(str);
// 骆                           昊
// e9        aa        86       e6       98       8a
// 11101001  10101010  10000110 11100110 10011000 10001010
for (int index = 0; index < buffer1.limit(); index += 1) {
    System.out.print(Integer.toHexString(buffer1.get(index) & 0xff) + " ");
}
System.out.println();
CharBuffer buffer2 = cs.decode(buffer1);
// 骆昊
System.out.println(buffer2.toString());

字符串的编解码

  Java中的String类提供了用字节数组和指定的编码构造字符串对象的操作,同时也提供了将字符串按照指定的编码解码成字节数组的操作,下面我们来做几个小实验。

实验1:中文变成’?’。

public static void main(String[] args) throws UnsupportedEncodingException {
    String str = "hello, 骆昊";
    byte[] buffer = str.getBytes("iso-8859-1");
    // hello, ??
    System.out.println(new String(buffer));
}

**说明:**ISO-8859-1是单字节编码,中文“骆昊”的编码(0x9a86和0x660a)会被转换成0x3f,而0x3f是ASCII码中的’?’,所以中文就变成了问号,而且中文字符的编码信息已经丢失,再怎么解码也没有机会还原出原来的中文字符了。所以这种现象也称之为“编码黑洞”,因为它把不认识的字符给吞噬掉了。很多Java的框架和产品默认都使用了ISO-8859-1,所以这个问题很常见。

实验2:中文变成看不懂的字符。

public static void main(String[] args) throws UnsupportedEncodingException {
    String str = "hello, 骆昊";
    byte[] buffer = str.getBytes("gbk");
    // hello, Âæê»
    System.out.println(new String(buffer, "iso-8859-1"));   
}

说明:这种情况在使用浏览器的时候也很常见,服务器传过来的是中文字符但是浏览器的编码却设置为ISO-8859-1就会出这种问题。

  如果中文经过了多次编解码,那么还有可能遇到一个中文字符变成多个问号的情况。其实要解决这些编码问题原则非常简单,首先如果要表示中文字符就不能使用单字节编码,这样势必会出现“黑洞”;其次编码和解码使用的“码”应当是一致的。

URL编码

  URL是统一资源定位符(Universal Resource Locator)的缩写,是Internet上标准的资源地址。它最初是由万维网和浏览器的发明者英国人Tim Berners-Lee发明用来作为万维网的地址,现在已经被W3C编制为Internet标准(RFC 1738)。统一资源定位符的标准格式如下:

协议://服务器域名或地址:[端口号]/资源路径/文件名[?查询参数]

  我们试一试在用谷歌搜索“骆昊”,来看看浏览器地址栏中的URL到底是什么样的。

https://www.google.com.hk/#safe=strict&q=%E9%AA%86%E6%98%8A

  URL中允许出现的字符分为保留字符(有特殊含义的字符)与未保留字符,未保留字符包括英文大小写字母、0-9的数字以及‘-’、 ‘_’、 ‘.’和’~’,保留字符包括 ‘!’、 ‘*’、 ‘’’、 ‘(’、 ‘)’、 ‘;’、 ‘:’、 ‘@’、 ‘&’、 ‘=’、 ‘+’、 ‘$’、 ‘,’、 ‘/’、 ‘?’、 ‘#’、 ‘[’和‘]’。如果URL中需要用到保留字符或者非URL允许的字符则需要使用百分号编码,例如:‘=’要处理成‘%3D’、‘+’要处理成‘%2B’、而上面要搜索的‘骆’和‘昊’两个中文字符被处理成了百分号编码的‘%E9%AA%86’和‘%E6%98%8A’。

  Java中要将URL中的非URL允许字符处理成百分号编码有非常简单的办法,就是使用URLEncoder类的encode方法,代码如下所示。

public static void main(String[] args) throws UnsupportedEncodingException {
    String urlStr = "Java 骆昊";
    String encodedUrlStr = URLEncoder.encode(urlStr, "utf-8");
    // Java+%E9%AA%86%E6%98%8A
    System.out.println(encodedUrlStr);
}

几种编码格式的比较

  表示中文可以选择的编码方式很多,包括GB2312、GBK、GB18030、UTF-8和UTF-16。UTF-16定义了Unicode字符在计算机中的存取方式,用固定长度的两个字节来表示所有的字符,Java中的char类型之所以是两个字节就是因为Java使用了UTF-16作为内存中字符存储的格式。UTF-16的编码效率高,字符与字节之间的转换也相对简单,但是如果在网络上传输数据的话会遇到大尾数和小尾数字节顺序转换的问题,因此UTF-8更适合在网络上传输数据,而UTF-16更适合在内存中使用。UTF-8使用了变长存储的方式,对ASCII字符采用单字节存储,对其他字符可以使用1~6个字节来表示,编码效率介于GBK和UTF-16之间,因此开发Java Web应用时,强烈建议使用UTF-8这种编码方式。


标签:编码,UTF,字节,字符,符号,乱码,Unicode,Java
From: https://blog.51cto.com/u_16166070/6535780

相关文章

  • Java Web项目中使用Freemarker生成Word文档
    Web项目中生成Word文档的操作屡见不鲜,基于Java的解决方案也是很多的,包括使用Jacob、ApachePOI、Java2Word、iText等各种方式,其实在从Office2003开始,就可以将Office文档转换成XML文件,这样只要将需要填入的内容放上${}占位符,就可以使用像Freemarker这样的模板引擎将出现占位符的地......
  • [连载]Java程序设计(05)---任务驱动方式:简单的加密/解密系统
    任务:还是上一家公司,现在该公司在全国各地都设立了自己的分公司以拓展其核心业务,那么就需要利用互联网在全国各地的公司之间传递信息(我们假定这些信息就是文字信息),这些信息可能涉及一些商业机密,为此公司需要一套简单的加密和解密系统来避免直接在互联网上传递明文信息。目前拟定了两......
  • 历史最全Java资源大全中文版整理分享
       很多程序员应该记得GitHub上有一个Awesome-XXX系列的资源整理。本资源对Java相关的资源列表进行翻译和整理,内容包括:构建工具、数据库、框架、模板、安全、代码分析、日志、第三方库、书籍、Java站点等。分享给需要的朋友。目录内容截图......
  • 第三次java博客
    在Java编程领域中,掌握如何读取和处理用户输入数据是非常基础和重要的一步。我们需要从用户那里获取输入数据,并根据输入数据进行相应的操作或计算。在这篇博客中,我们将会探讨如何在Java程序中读取和处理用户输入数据,以及如何利用这些数据来完成一些常见的任务,比如计算平均分和总成......
  • java Condition类的详细介绍
    一、condition介绍及demoCondition是在java1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列......
  • JAVA:Collections类的shuffle()方法
    Java.util.Collections类下有一个静态的shuffle()方法,如下:1)staticvoidshuffle(List<?>list)使用默认随机源对列表进行置换,所有置换发生的可能性都是大致相等的。2)staticvoidshuffle(List<?>list,Randomrand)使用指定的随机源对指定列表进行置换,所有置换发生的可能性......
  • org.springframework.boot.builder.SpringApplicationBuilder.init([LjavalangObject;
    一SpringBoot2.0.4集成SpringCloud异常:org.springframework.boot.builder.SpringApplicationBuilder.([Ljava/lang/Object;)V二、异常处理参考:缘起初学springcloud的朋友可能不知道,其实SpringBoot与SpringCloud需要版本对应,否则可能会造成很多意料之外的错误,比如eureka注册了......
  • Java 基本数据类型 - 四类八种
    感谢:https://zhuanlan.zhihu.com/p/25439066八种基本数据类型分成四个大类1、整型byte、short、int、long2、浮点型float、double3、字符型char4、布尔型boolean数据类型详细介绍整型(byte、short、int、long)虽然byte、short、int、long数据类型都......
  • Java intern函数详解
    先看一个例子如果你会了那这篇文章你没必要看了,如果不会那请看下去,你一定会有收获:Strings=newString("hello");Stringstr1=s+"world";Stringstr3="helloworld";system.out.println(srt1==str3);Strings=newString("hello");Stringstr1=s+&q......
  • Java读取excel中日期格式结果为数字xxx天
    解释:Java读取excel中日期结果是计算1900-0-1之后到当前日期,一共有多少天,需要做一下转换处理/***用于计算1900-0-1之后的day天日期是哪天*举例:1900-0-1之后的44326天日期是2021/5/10*@return*/publicstaticStringdayToDate(intday){......