当前位置: 首页 > 编程笔记 >

深入解析Java中的编码转换以及编码和解码操作

曹兴贤
2023-03-14
本文向大家介绍深入解析Java中的编码转换以及编码和解码操作,包括了深入解析Java中的编码转换以及编码和解码操作的使用技巧和注意事项,需要的朋友参考一下

一、Java编码转换过程
 我们总是用一个java类文件和用户进行最直接的交互(输入、输出),这些交互内容包含的文字可能会包含中文。无论这些java类是与数据库交互,还是与前端页面交互,他们的生命周期总是这样的:
 (1)、程序员在操作系统上通过编辑器编写程序代码并且以.java的格式保存操作系统中,这些文件我们称之为源文件。
 (2)、通过JDK中的javac.exe编译这些源文件形成.class类。
 (3)、直接运行这些类或者部署在WEB容器中运行,得到输出结果。
 这些过程是从宏观上面来观察的,了解这个肯定是不行的,我们需要真正来了解java是如何来编码和被解码的:
 第一步:当我们用编辑器编写java源文件,程序文件在保存时会采用操作系统默认的编码格式(一般我们中文的操作系统采用的是GBK编码格式)形成一个.java文件。java源文件是采用操作系统默认支持的file.encoding编码格式保存的。下面代码可以查看系统的file.encoding参数值。

System.out.println(System.getProperty("file.encoding")); 

 第二步:当我们使用javac.exe编译我们的java文件时,JDK首先会确认它的编译参数encoding来确定源代码字符集,如果我们不指定该编译参数,JDK首先会获取操作系统默认的file.encoding参数,然后JDK就会把我们编写的java源程序从file.encoding编码格式转化为JAVA内部默认的UNICODE格式放入内存中。
 第三步:JDK将上面编译好的且保存在内存中信息写入class文件中,形成.class文件。此时.class文件是Unicode编码的,也就是说我们常见的.class文件中的内容无论是中文字符还是英文字符,他们都已经转换为Unicode编码格式了。
 在这一步中对对JSP源文件的处理方式有点儿不同:WEB容器调用JSP编译器,JSP编译器首先会查看JSP文件是否设置了文件编码格式,如果没有设置则JSP编译器会调用调用JDK采用默认的编码方式将JSP文件转化为临时的servlet类,然后再编译为.class文件并保持到临时文件夹中。
 第四步:运行编译的类:在这里会存在一下几种情况
 (1)、直接在console上运行。
 (2)、JSP/Servlet类。
 (3)、java类与数据库之间。
 这三种情况每种情况的方式都会不同,
1.Console上运行的类
 这种情况下,JVM首先会把保存在操作系统中的class文件读入到内存中,这个时候内存中class文件编码格式为Unicode,然后JVM运行它。如果需要用户输入信息,则会采用file.encoding编码格式对用户输入的信息进行编码同时转换为Unicode编码格式保存到内存中。程序运行后,将产生的结果再转化为file.encoding格式返回给操作系统并输出到界面去。整个流程如下:

在上面整个流程中,凡是涉及的编码转换都不能出现错误,否则将会产生乱码。
2.Servlet类
 由于JSP文件最终也会转换为servlet文件(只不过存储的位置不同而已),所以这里我们也将JSP文件纳入其中。
 当用户请求Servlet时,WEB容器会调用它的JVM来运行Servlet。首先JVM会把servlet的class加载到内存中去,内存中的servlet代码是Unicode编码格式的。然后JVM在内存中运行该Servlet,在运行过程中如果需要接受从客户端传递过来的数据(如表单和URL传递的数据),则WEB容器会接受传入的数据,在接收过程中如果程序设定了传入参数的的编码则采用设定的编码格式,如果没有设置则采用默认的ISO-8859-1编码格式,接收的数据后JVM会将这些数据进行编码格式转换为Unicode并且存入到内存中。运行Servlet后产生输出结果,同时这些输出结果的编码格式仍然为Unicode。紧接着WEB容器会将产生的Unicode编码格式的字符串直接发送置客户端,如果程序指定了输出时的编码格式,则按照指定的编码格式输出到浏览器,否则采用默认的ISO-8859-1编码格式。整个过程流程图如下:

3.数据库部分
 我们知道java程序与数据库的连接都是通过JDBC驱动程序来连接的,而JDBC驱动程序默认的是ISO-8859-1编码格式的,也就是说我们通过java程序向数据库传递数据时,JDBC首先会将Unicode编码格式的数据转换为ISO-8859-1的编码格式,然后在存储在数据库中,即在数据库保存数据时,默认格式为ISO-8859-1。

二、编码&解码
下面将结束java在那些场合需要进行编码和解码操作,并详序中间的过程,进一步掌握java的编码和解码过程。在java中主要有四个场景需要进行编码解码操作:
 (1):I/O操作
 (2):内存
 (3):数据库
 (4):javaWeb
 下面主要介绍前面两种场景,数据库部分只要设置正确编码格式就不会有什么问题,javaWeb场景过多需要了解URL、get、POST的编码,servlet的解码,所以javaWeb场景下节LZ介绍。
1.I/O操作
 在前面LZ就提过乱码问题无非就是转码过程中编码格式的不统一产生的,比如编码时采用UTF-8,解码采用GBK,但最根本的原因是字符到字节或者字节到字符的转换出问题了,而这中情况的转换最主要的场景就是I/O操作的时候。当然I/O操作主要包括网络I/O(也就是javaWeb)和磁盘I/O。网络I/O下节介绍。
 首先我们先看I/O的编码操作。

InputStream为字节输入流的所有类的超类,Reader为读取字符流的抽象类。java读取文件的方式分为按字节流读取和按字符流读取,其中InputStream、Reader是这两种读取方式的超类。
 按字节
 我们一般都是使用InputStream.read()方法在数据流中读取字节(read()每次都只读取一个字节,效率非常慢,我们一般都是使用read(byte[])),然后保存在一个byte[]数组中,最后转换为String。在我们读取文件时,读取字节的编码取决于文件所使用的编码格式,而在转换为String过程中也会涉及到编码的问题,如果两者之间的编码格式不同可能会出现问题。例如存在一个问题test.txt编码格式为UTF-8,那么通过字节流读取文件时所获得的数据流编码格式就是UTF-8,而我们在转化成String过程中如果不指定编码格式,则默认使用系统编码格式(GBK)来解码操作,由于两者编码格式不一致,那么在构造String过程肯定会产生乱码,如下:

File file = new File("C:\\test.txt"); 
InputStream input = new FileInputStream(file); 
StringBuffer buffer = new StringBuffer(); 
byte[] bytes = new byte[1024]; 
for(int n ; (n = input.read(bytes))!=-1 ; ){ 
 buffer.append(new String(bytes,0,n)); 
} 
System.out.println(buffer); 

输出结果为乱码....
test.txt中的内容为:我是 cm。
 要想不出现乱码,在构造String过程中指定编码格式,使得编码解码时两者编码格式保持一致即可:

buffer.append(new String(bytes,0,n,"UTF-8")); 

 按字符
 其实字符流可以看做是一种包装流,它的底层还是采用字节流来读取字节,然后它使用指定的编码方式将读取字节解码为字符。在java中Reader是读取字符流的超类。所以从底层上来看按字节读取文件和按字符读取没什么区别。在读取的时候字符读取每次是读取留个字节,字节流每次读取一个字节。
 字节&字符转换
 字节转换为字符一定少不了InputStreamReader。API解释如下:InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。 每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。API解释非常清楚,InputStreamReader在底层读取文件时仍然采用字节读取,读取字节后它需要根据一个指定的编码格式来解析为字符,如果没有指定编码格式则采用系统默认编码格式。

String file = "C:\\test.txt"; 
   String charset = "UTF-8"; 
   // 写字符换转成字节流 
   FileOutputStream outputStream = new FileOutputStream(file); 
   OutputStreamWriter writer = new OutputStreamWriter(outputStream, charset); 
   try { 
   writer.write("我是 cm"); 
   } finally { 
   writer.close(); 
   } 
   
   // 读取字节转换成字符 
   FileInputStream inputStream = new FileInputStream(file); 
   InputStreamReader reader = new InputStreamReader( 
   inputStream, charset); 
   StringBuffer buffer = new StringBuffer(); 
   char[] buf = new char[64]; 
   int count = 0; 
   try { 
   while ((count = reader.read(buf)) != -1) { 
    buffer.append(buf, 0, count); 
   } 
   } finally { 
   reader.close(); 
   } 
   System.out.println(buffer); 

2.内存
 首先我们看下面这段简单的代码

String s = "我是 cm"; 
byte[] bytes = s.getBytes(); 
String s1 = new String(bytes,"GBK"); 
String s2 = new String(bytes); 

 在这段代码中我们看到了三处编码转换过程(一次编码,两次解码)。先看String.getTytes():

public byte[] getBytes() { 
  return StringCoding.encode(value, 0, value.length); 
 } 

 内部调用StringCoding.encode()方法操作:

static byte[] encode(char[] ca, int off, int len) { 
  String csn = Charset.defaultCharset().name(); 
  try { 
   // use charset name encode() variant which provides caching. 
   return encode(csn, ca, off, len); 
  } catch (UnsupportedEncodingException x) { 
   warnUnsupportedCharset(csn); 
  } 
  try { 
   return encode("ISO-8859-1", ca, off, len); 
  } catch (UnsupportedEncodingException x) { 
   // If this code is hit during VM initialization, MessageUtils is 
   // the only way we will be able to get any kind of error message. 
   MessageUtils.err("ISO-8859-1 charset not available: " 
        + x.toString()); 
   // If we can not find ISO-8859-1 (a required encoding) then things 
   // are seriously wrong with the installation. 
   System.exit(1); 
   return null; 
  } 
 } 

 encode(char[] paramArrayOfChar, int paramInt1, int paramInt2)方法首先调用系统的默认编码格式,如果没有指定编码格式则默认使用ISO-8859-1编码格式进行编码操作,进一步深入如下:

String csn = (charsetName == null) ? "ISO-8859-1" : charsetName; 

 同样的方法可以看到new String 的构造函数内部是调用StringCoding.decode()方法:

public String(byte bytes[], int offset, int length, Charset charset) { 
  if (charset == null) 
   throw new NullPointerException("charset"); 
  checkBounds(bytes, offset, length); 
  this.value = StringCoding.decode(charset, bytes, offset, length); 
 } 

 decode方法和encode对编码格式的处理是一样的。
 对于以上两种情况我们只需要设置统一的编码格式一般都不会产生乱码问题。
3.编码&编码格式
 首先先看看java编码类图

首先根据指定的chart设置ChartSet类,然后根据ChartSet创建ChartSetEncoder对象,最后再调用 CharsetEncoder.encode 对字符串进行编码,不同的编码类型都会对应到一个类中,实际的编码过程是在这些类中完成的。下面时序图展示详细的编码过程:

通过这编码的类图和时序图可以了解编码的详细过程。下面将通过一段简单的代码对ISO-8859-1、GBK、UTF-8编码

public class Test02 { 
 public static void main(String[] args) throws UnsupportedEncodingException { 
  String string = "我是 cm"; 
  Test02.printChart(string.toCharArray()); 
  Test02.printChart(string.getBytes("ISO-8859-1")); 
  Test02.printChart(string.getBytes("GBK")); 
  Test02.printChart(string.getBytes("UTF-8")); 
 } 
  
 /** 
  * char转换为16进制 
  */ 
 public static void printChart(char[] chars){ 
  for(int i = 0 ; i < chars.length ; i++){ 
   System.out.print(Integer.toHexString(chars[i]) + " "); 
  } 
  System.out.println(""); 
 } 
  
 /** 
  * byte转换为16进制 
  */ 
 public static void printChart(byte[] bytes){ 
  for(int i = 0 ; i < bytes.length ; i++){ 
   String hex = Integer.toHexString(bytes[i] & 0xFF); 
    if (hex.length() == 1) { 
    hex = '0' + hex; 
    } 
    System.out.print(hex.toUpperCase() + " "); 
  } 
  System.out.println(""); 
 } 
} 

输出:

6211 662f 20 63 6d 
3F 3F 20 63 6D 
CE D2 CA C7 20 63 6D 
E6 88 91 E6 98 AF 20 63 6D 

 通过程序我们可以看到“我是 cm”的结果为:

 char[]:6211 662f 20 63 6d
 ISO-8859-1:3F 3F 20 63 6D 
 GBK:CE D2 CA C7 20 63 6D 
 UTF-8:E6 88 91 E6 98 AF 20 63 6D

 图如下:

 类似资料:
  • 编码是将字符,数字和其他特殊字符等字符序列放入专用格式以进行有效传输的过程。 解码是将编码格式转换回原始字符序列的过程。它与我们通常误解的加密完全不同。编码和解码用于数据通信和存储。编码不应用于传输敏感信息。 URL编码 URL只能使用ASCII字符集通过Internet发送,并且在URL包含除ASCII字符之外的特殊字符的情况下,需要对其进行编码。网址不包含空格,并替换为加号(+)或。 ASCI

  • 主要内容:URL基本组成,哪些字符需要编码,Python实现编码与解码,Python 的标准库urllib.parse模块中提供了用来编码和解码的方法,分别是 urlencode() 与 unquote() 方法。当 URL 路径或者查询参数中,带有中文或者特殊字符的时候,就需要对 URL 进行编码(采用十六进制编码格式)。URL 编码的原则是使用安全字符去表示那些不安全的字符。 安全字符,指的是没有特殊用途或者特殊意义的字符。 URL基本组成 URL 是由一些简单的组件构成,比如协议、域名、端

  • 问题内容: 有没有免费的Java库,可以将一种编码格式的字符串转换为其他编码格式,例如iconv?我正在使用Java版本1.3。 问题答案: 你不需要标准库以外的库,只需使用Charset即可。(你可以只使用String构造函数和getBytes方法,但就我个人而言,我不喜欢仅使用字符编码的名称。输入错误的空间太大。) 编辑:正如注释中指出的那样,你仍然可以使用Charset实例,但是可以轻松使用

  • 问题内容: 我试图最终解决一些编码问题,这些问题从尝试使用lxml抓取HTML弹出。这是我遇到的三个示例HTML文档: 1。 2。 3。 我的基本脚本: 结果是: 因此,显然是样本1和缺少标签的问题。这里的解决方案将正确地将示例1识别为utf-8,因此在功能上与我的原始代码等效。 lxml文档出现冲突: 从这里开始,该示例似乎建议我们应该使用UnicodeDammit将标记编码为unicode。

  • 本文向大家介绍Java实现二维码QRCode的编码和解码与示例解析,包括了Java实现二维码QRCode的编码和解码与示例解析的使用技巧和注意事项,需要的朋友参考一下 Java实现二维码QRCode的编码和解码 涉及到的一些主要类库,方便大家下载: 编码lib:Qrcode_swetake.jar   (官网介绍-- http://www.swetake.com/qr/index-e.html) 

  • 问题内容: 编码URL字符串以使其符合rfc2396并解码与rfc2396兼容的字符串(例如,将%20替换为空格字符)的最佳方法是什么? 编辑:URLEncoder的和URLDecoder类做 不 编码/解码RFC2396兼容网址,它们编码到一个MIME类型application / x-WWW窗体-urlencoded的其用于编码HTML表单参数数据。 问题答案: 使用URI类,如下所示: 或者