【JAVA编码专题】总结

第一部分:编码基础

为什么需要编码:用计算机看得懂的语言(二进制数)表示各种各样的字符。

一、基本概念

ASCII、Unicode、big5、GBK等为字符集,它们只定义了这个字符集内有哪些字符,以及分别用什么数字表示。

而UTF-8与UTF-16则定义了Unicode字符集如何使用计算机看得懂的语言进行传输和保存。

例如: Unicode 字符 U+00A9 = 1010 1001 (版权符号) 在 UTF-8 里的编码为:

11000010 10101001 = 0xC2 0xA9

事实上,没有必要将它们严格区分,一些字符集本身就是编码方式,如ASCII。它们均表示如何用二进制数表示一个字符。

因此很多地方将UTF-8、UTF-16与ASCII、GBK等统一当作字符编码方式。

二、常见编码

明白了各种语言需要交流,经过翻译是必要的,那又如何来翻译呢?计算中提拱了多种翻译方式,常见的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。它们都可以被看作为字典,它们规定了转化的规则,按照这个规则就可以让计算机正确的表示我们的字符。目前的编码格式很多,例如 GB2312、GBK、UTF-8、UTF-16 这几种格式都可以表示一个汉字,那我们到底选择哪种编码格式来存储汉字呢?这就要考虑到其它因素了,是存储空间重要还是编码的效率重要。根据这些因素来正确选择编码格式,下面简要介绍一下这几种编码格式。

ASCII 码

学过计算机的人都知道 ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。

ISO-8859-1

128 个字符显然是不够用的,于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。

GB2312

它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。

GBK

全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。

GB18030

全称是《信息交换用汉字编码字符集》,是我国的强制标准,它可能是单字节、双字节或者四字节编码,它的编码与 GB2312 编码兼容,这个虽然是国家标准,但是实际应用系统中使用的并不广泛。

UTF-16

说到 UTF 必须要提到 Unicode(Universal Code 统一码),ISO 试图想创建一个全新的超语言字典,世界上所有的语言都可以通过这本字典来相互翻译。可想而知这个字典是多么的复杂,关于 Unicode 的详细规范可以参考相应文档。Unicode 是 Java 和 XML 的基础,下面详细介绍 Unicode 在计算机中的存储形式。

UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。

UTF-8

UTF16固定使用2个字节(或者4个字节)来表示字符,这导致与早期大量使用的ASCII码无法兼容,同时一些特殊字符在UNIX系统中存在特殊含义,如 ‘/0‘ 或 ‘/‘, 它们在 文件名和其他 C 库函数参数里都有特别的含义。此外,一些最常用的字符(西欧字符)只用一个字节就可以表示,若使用UTF16则浪费带宽或者存储空间。

三、 UTF-8详细介绍

首先 UCS 和 Unicode 只是分配整数给字符的编码表. 现在存在好几种将一串字符表示为一串字节的方法. 最显而易见的两种方法是将 Unicode 文本存储为 2 个 或 4 个字节序列的串. 这两种方法的正式名称分别为 UCS-2 和 UCS-4. 除非另外指定, 否则大多数的字节都是这样的(Bigendian convention). 将一个 ASCII 或 Latin-1 的文件转换成 UCS-2 只需简单地在每个 ASCII 字节前插入 0x00. 如果要转换成 UCS-4, 则必须在每个 ASCII
字节前插入三个 0x00.

在 Unix 下使用 UCS-2 (或 UCS-4) 会导致非常严重的问题. 用这些编码的字符串会包含一些特殊的字符, 比如 ‘/0‘ 或 ‘/‘, 它们在 文件名和其他 C 库函数参数里都有特别的含义. 另外, 大多数使用 ASCII 文件的 UNIX 下的工具, 如果不进行重大修改是无法读取 16 位的字符的. 基于这些原因, 在文件名, 文本文件, 环境变量等地方, UCS-2 不适合作为 Unicode 的外部编码.

在 ISO 10646-1 Annex R 和 RFC 2279 里定义的 UTF-8 编码没有这些问题. 它是在 Unix 风格的操作系统下使用 Unicode 的明显的方法.

UTF-8 有一下特性:

UCS 字符 U+0000 到 U+007F (ASCII) 被编码为字节 0x00 到 0x7F (ASCII 兼容). 这意味着只包含 7 位 ASCII 字符的文件在 ASCII 和 UTF-8 两种编码方式下是一样的.

所有 >U+007F 的 UCS 字符被编码为一个多个字节的串, 每个字节都有标记位集. 因此, ASCII 字节 (0x00-0x7F) 不可能作为任何其他字符的一部分.

表示非 ASCII 字符的多字节串的第一个字节总是在 0xC0 到 0xFD 的范围里, 并指出这个字符包含多少个字节. 多字节串的其余字节都在 0x80 到 0xBF 范围里. 这使得重新同步非常容易, 并使编码无国界, 且很少受丢失字节的影响.

可以编入所有可能的 231个 UCS 代码

UTF-8 编码字符理论上可以最多到 6 个字节长, 然而 16 位 BMP 字符最多只用到 3 字节长.

Bigendian UCS-4 字节串的排列顺序是预定的.

字节 0xFE 和 0xFF 在 UTF-8 编码中从未用到.

下列字节串用来表示一个字符. 用到哪个串取决于该字符在 Unicode 中的序号.

U-00000000 - U-0000007F:    0xxxxxxx

U-00000080 - U-000007FF:    110xxxxx 10xxxxxx

U-00000800 - U-0000FFFF:    1110xxxx 10xxxxxx 10xxxxxx

U-00010000 - U-001FFFFF:    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

U-00200000 - U-03FFFFFF:    111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

U-04000000 - U-7FFFFFFF:    1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

xxx 的位置由字符编码数的二进制表示的位填入. 越靠右的 x 具有越少的特殊意义. 只用最短的那个足够表达一个字符编码数的多字节串. 注意在多字节串中, 第一个字节的开头"1"的数目就是整个串中字节的数目.

例如: Unicode 字符 U+00A9 = 1010 1001 (版权符号) 在 UTF-8 里的编码为:

11000010 10101001 = 0xC2 0xA9

而字符 U+2260 = 0010 0010 0110 0000 (不等于) 编码为:

11100010 10001001 10100000 = 0xE2 0x89 0xA0

这种编码的官方名字拼写为 UTF-8, 其中 UTF 代表 UCS Transformation Format. 请勿在任何文档中用其他名字 (比如 utf8 或 UTF_8) 来表示 UTF-8, 当然除非你指的是一个变量名而不是这种编码本身.

第二部分:JAVA编码

一、String中的编码

1、JVM的默认编码为UTF-8

		System.out.println(Charset.defaultCharset());

输出:UTF-8

2、获取String的编码

可以通过public byte[] getBytes(String charsetName) throws UnsupportedEncodingException获取某个String的编码

		// 二、获取String在各种编码格式中的编码
		String s = "aZ中文";
		// 1、默认的Unicode编码
		byte[] bytesDefault = s.getBytes();
		System.out.println(getHexString(bytesDefault));

		// 2、也是默认的编码
		byte[] bytesDefault2 = s.getBytes(Charset.defaultCharset());
		System.out.println(getHexString(bytesDefault2));

		// 3、UTF-8编码
		byte[] bytesUTF8 = s.getBytes("UTF-8");
		System.out.println(getHexString(bytesUTF8));

		// 4、UTF-16编码
		byte[] bytesUTF16 = s.getBytes("UTF-16");
		System.out.println(getHexString(bytesUTF16));

		// 5、unicode编码
		byte[] bytesUnicode = s.getBytes("UNICODE");
		System.out.println(getHexString(bytesUnicode));

		// 6、GBK编码
		byte[] bytesGBK = s.getBytes("GBK");
		System.out.println(getHexString(bytesGBK));

输出:

615ae4b8ade69687

615ae4b8ade69687

615ae4b8ade69687

feff0061005a4e2d6587

feff0061005a4e2d6587

615ad6d0cec4

由此可以看出,unicode与utf16可以认为是同一方式。

3、通过byte[]编码构建String

public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException

用什么方式生成的编码,就应该用原有的方式进行还原 

		// 三、通过byte[]编码构建String,用什么方式生成的编码,就应该用原有的方式进行还原
		// 1、默认编码
		String sDefault = new String(bytesDefault);
		System.out.println(sDefault);

		// 2、默认编码2
		String sDefault2 = new String(bytesDefault2, Charset.defaultCharset());
		System.out.println(sDefault2);

		// 3、UTF-8编码
		String sUTF8 = new String(bytesUTF8, "UTF8");
		System.out.println(sUTF8);

		// 4、UTF-16编码
		String sUTF16 = new String(bytesUTF16, "UTF16");
		System.out.println(sUTF16);

		// 5、unicode编码
		String sUnicode = new String(bytesUnicode, "Unicode");
		System.out.println(sUnicode);

		// 6、GBK编码
		String sGBK = new String(bytesGBK, "GBK");
		System.out.println(sGBK);

输出:

aZ中文

aZ中文

aZ中文

aZ中文

aZ中文

aZ中文

若用其它编码格式来构建String,则出现乱码

		// 若通过其它编码方式进行还原,则出现乱码
		// 1、用UTF-16编码解码UTF8生成的字节
		String sUTF16Erro = new String(bytesDefault, "UTF16");
		System.out.println(sUTF16Erro);

		// 2、用unicode编码解码UTF8生成的字节
		String sUnicodeError = new String(bytesDefault, "Unicode");
		System.out.println(sUnicodeError);

		// 3、用GBK编码解码UTF8生成的字节
		String sGBKError = new String(bytesDefault, "GBK");
		System.out.println(sGBKError);

输出:

慚?隇

慚?隇

aZ涓枃

附完整代码及输出

package org.ljh.javademo.encode;

import java.io.IOException;
import java.nio.charset.Charset;

public class DefaultEncode {

	public static void main(String[] args) throws IOException {
		// 一、获取当前JVM环境的默认编码
		System.out.println(Charset.defaultCharset());

		// 二、获取String在各种编码格式中的编码
		String s = "aZ中文";
		// 1、默认的Unicode编码
		byte[] bytesDefault = s.getBytes();
		System.out.println(getHexString(bytesDefault));

		// 2、也是默认的编码
		byte[] bytesDefault2 = s.getBytes(Charset.defaultCharset());
		System.out.println(getHexString(bytesDefault2));

		// 3、UTF-8编码
		byte[] bytesUTF8 = s.getBytes("UTF-8");
		System.out.println(getHexString(bytesUTF8));

		// 4、UTF-16编码
		byte[] bytesUTF16 = s.getBytes("UTF-16");
		System.out.println(getHexString(bytesUTF16));

		// 5、unicode编码
		byte[] bytesUnicode = s.getBytes("UNICODE");
		System.out.println(getHexString(bytesUnicode));

		// 6、GBK编码
		byte[] bytesGBK = s.getBytes("GBK");
		System.out.println(getHexString(bytesGBK));

		// 三、通过byte[]编码构建String,用什么方式生成的编码,就应该用原有的方式进行还原
		// 1、默认编码
		String sDefault = new String(bytesDefault);
		System.out.println(sDefault);

		// 2、默认编码2
		String sDefault2 = new String(bytesDefault2, Charset.defaultCharset());
		System.out.println(sDefault2);

		// 3、UTF-8编码
		String sUTF8 = new String(bytesUTF8, "UTF8");
		System.out.println(sUTF8);

		// 4、UTF-16编码
		String sUTF16 = new String(bytesUTF16, "UTF16");
		System.out.println(sUTF16);

		// 5、unicode编码
		String sUnicode = new String(bytesUnicode, "Unicode");
		System.out.println(sUnicode);

		// 6、GBK编码
		String sGBK = new String(bytesGBK, "GBK");
		System.out.println(sGBK);

		// 若通过其它编码方式进行还原,则出现乱码
		// 1、用UTF-16编码解码UTF8生成的字节
		String sUTF16Erro = new String(bytesDefault, "UTF16");
		System.out.println(sUTF16Erro);

		// 2、用unicode编码解码UTF8生成的字节
		String sUnicodeError = new String(bytesDefault, "Unicode");
		System.out.println(sUnicodeError);

		// 3、用GBK编码解码UTF8生成的字节
		String sGBKError = new String(bytesDefault, "GBK");
		System.out.println(sGBKError);

	}

	// 输入byte[],将之转化为16进制的字符进行输出,如输入{90,20,21},则返回5A1415。因为默认情况下byte以10进制格式进行输出
	private static String getHexString(byte[] b) {
		String hexs = "";
		for (int i = 0; i < b.length; i++) {
			String hex = Integer.toHexString(b[i] & 0xFF);
			if (hex.length() == 1) {
				hex = '0' + hex;
			}
			hexs += hex;
		}
		return hexs;
	}

}

输出:

UTF-8

615ae4b8ade69687

615ae4b8ade69687

615ae4b8ade69687

feff0061005a4e2d6587

feff0061005a4e2d6587

615ad6d0cec4

aZ中文

aZ中文

aZ中文

aZ中文

aZ中文

aZ中文

慚?隇

慚?隇

aZ涓枃

二、JAVA IO中的编码

时间: 2024-10-09 01:39:47

【JAVA编码专题】总结的相关文章

java编码规范

右括号") "与其后面的关键字之间,关键字与其后面的左括号"("或"{"之间,以及"}"与"{"之间,要以一个空格隔开:除". "外,所有二元操作符的前.后要加空格:在逗号后边加一个空格. 说明: 一个紧跟着括号的关键词应该被空格分开: 空白应该位于参数列表中逗号的后面: 所有的二元运算符,除了".",应该使用空格将之与操作数分开.一元操作符和操作数之间不应该加空格,

阿里Java编码规范

详细,全面 很不错 阿里 Java编码规范

JAVA技术专题综述之线程篇(1)

本文详细介绍JAVA技术专题综述之线程篇 编写具有多线程能力的程序经常会用到的方法有: run(),start(),wait(),notify(),notifyAll(),sleep(),yield(),join() 还有一个重要的关键字:synchronized 本文将对以上内容进行讲解. 一:run()和start() 示例1: public cla ThreadTest extends Thread{public void run(){for(int i=0;i<10;i++){Syste

java编码问题

工作中经常遇到java编码问题,由于缺乏研究,总是无法给出确切的答案,这个周末在网上查了一些资料,在此做些汇总. 问题一:在java中读取文件时应该采用什么编码? Java读取文件的方式总体可以分为两类:按字节读取和按字符读取.按字节读取就是采用InputStream.read()方法来读取字节,然后保存到一个byte[]数组中,最后经常用new String(byte[]);把字节数组转换成String.在最后一步隐藏了一个编码的细节,new String(byte[]);会使用操作系统默认的

Java编码问题汇总

转自 http://www.blogjava.net/zhangchao/archive/2011/05/26/351051.html Thanks Java编码问题汇总 工作中经常遇到java编码问题,由于缺乏研究,总是无法给出确切的答案,这个周末在网上查了一些资料,在此做些汇总. 问题一:在java中读取文件时应该采用什么编码? Java读取文件的方式总体可以分为两类:按字节读取和按字符读取.按字节读取就是采用InputStream.read()方法来读取字节,然后保存到一个byte[]数组

Java编码

转自:http://www.blogjava.net/zhangchao/archive/2011/05/26/351051.html Thanks Java编码问题汇总 工作中经常遇到java编码问题,由于缺乏研究,总是无法给出确切的答案,这个周末在网上查了一些资料,在此做些汇总. 问题一:在java中读取文件时应该采用什么编码? Java读取文件的方式总体可以分为两类:按字节读取和按字符读取.按字节读取就是采用InputStream.read()方法来读取字节,然后保存到一个byte[]数组

关于Java编码

ASCII:用7位编码将英文字符和一些常用的符号存诸为从0到127的数值. Unicode:Unicode用一个2字节数字表示每个字符,从0到65535.每个 2 字节数字表示至少在一种世界语言中使用的一个唯一字符.(在多种语言中都使用的字符具有相同的数字码.)这样就确保每个字符一个数字,并且每个数字一个字符.Unicode数据永远不会模棱两可.Unicode使用相同的数字表示ASCII和ISO-8859-1中的字符.只是这两种编码用一个字节表示,而Unicode用两个字节表示.所以Unicod

Java 并发专题 : Executor详细介绍 打造基于Executor的Web服务器

适配器模式,将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 应用场景:系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配.适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况. 代码实现: //Adapter.h #include "stdafx.h" #include <iostream> class Adaptee

Java 并发专题 :闭锁 CountDownLatch 之一家人一起吃个饭

最近一直整并发这块东西,顺便写点Java并发的例子,给大家做个分享,也强化下自己记忆. 每天起早贪黑的上班,父母每天也要上班,话说今天定了个饭店,一家人一起吃个饭,通知大家下班去饭店集合.假设:3个人在不同的地方上班,必须等到3个人到场才能吃饭,用程序如何实现呢? 作为一名资深屌丝程序猿,开始写代码实现: package com.zhy.concurrency.latch; public class Test1 { /** * 模拟爸爸去饭店 */ public static void fath