admin 管理员组

文章数量: 1086866

字符集与字符编码,java中的char和unicode

文章目录

      • 基本单位转换
      • 进制缩写与表示
      • 字符、字符集、字符编码
      • ASCII码
      • 全角
      • GBK、GB2312
      • 为什么需要字符编码
      • UTF-8和Unicode的关系
      • java中的char 和unicode
      • mysql中使用utf8
      • iso-8859-1
      • 为什么会出现乱码
      • Hex
      • 二进制流,二进制文件
      • 为什么用二进制文件
      • 为什么http是文本传输协议还能传图片和文件?

基本单位转换

位bit 最小的单元
字节byte 机器语言的单位1byte=8bits
1KB=1024byte
1MB=1024KB
1GB=1024MB

进制缩写与表示

二进制 binary Bin
八进制 octal OCT 0
十进制 decimal DEC
十六进制 hexadecimal HEX 0X或0x

字符、字符集、字符编码

字符:是各种文字和符号的总称,包括各个国家的文字,标点符号,图形符号,数字,emoji等
字符集:指编码字符集,比如Unicode,ASCII,GB2312,GBK等,每一个字符,都对应到唯一的一个代码值(code point)
字符编码:字符集只是规定了有哪些字符,是编码字符集的字符和实际的存储值之间的转换关系,有UTF-8,UTF-16,UTF-32
其中ANSI在中国大陆即为GBK(以前是GB2312),在英文操作系统则是ISO-8859-1

UCS-2即为人们常说的Unicode编码,又分为大端、小端

所谓BOM头(Byte Order Mark)就是文本文件中开始的几个并不表示任何字符的字节,用二进制编辑器(如bz.exe)就能看到了
UTF8的BOM头为0xEF 0xBB 0xBF
Unicode大端模式为0xFE 0xFF
Unicode小端模式为0xFF 0xFE

说明:notepadd++需要添加插件HEX-Editor

ASCII码

单字节编码
基本ASCII:0-127

32是空格,其中48~57为0到9 阿拉伯数字。
65-90为26个大写英文字母,97~122号为26个小写英文字母
对于其他语言来说 256个字符不够用!

全角

全角是一种电脑字符,且每个全角字符占用两个标准字符位置。通常的英文字母、数字键、符号键都是半角的,半角的显示内码都是一个字节。为了排列整齐,英文和其它拉丁文的字符和标点也提供了全角格式。在中文输入法中,切换全角和半角格式的快捷键为SHIFT+空格

虽然通过使用不同字符集,我们可以在一台机器上查阅不同语言的文档,但是我们仍然无法解决一个问题:如果一份文档中含有不同国家的不同语言的字符,那么无法在一份文档中显示所有字符

GBK、GB2312

GB2312采用两个字节来表示一个字符,几乎所有的中文系统和国际化的软件都支持的中文字符集

GBK完全兼容GB2312,还对繁体中文和一些不常用的汉字和许多符号进行了编码,是现阶段Windows和其他一些中文操作系统的默认字符集,但并不是所有的国际化软件都支持该字符集。
GBK用最高位为1,表示两个字节(中文),0表示1个字节(英文)

为什么需要字符编码

要正确编码转码一个字符需要三个关键元素:字库表(character repertoire)、编码字符集(coded character set)、字符编码(character encoding form)。
其中字库表是一个相当于所有可读或者可显示字符的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围。
编码字符集,即给每个字符一个编码值,用一个编码值code point来表示一个字符在字库中的 位置
字符编码,将编码字符集和实际存储数值之间的转换关系。一般来说都会直接将code point的值作为编码后的值直接存储。例如在ASCII中A在表中排第65位,而编码后A的数值是0100 0001也即十进制的65的二进制转换结果
看到这里,可能很多读者都会有和我当初一样的疑问:字库表和编码字符集看来是必不可少的,那既然字库表中的每一个字符都有一个自己的序号,直接把序号作为存储内容就好了。为什么还要多此一举通过字符编码把序号转换成另外一种存储格式呢?其实原因也比较容易理解:统一字库表的目的是为了能够涵盖世界上所有的字符,但实际使用过程中会发现真正用的上的字符相对整个字库表来说比例非常低。例如中文地区的程序几乎不会需要日语字符,而一些英语国家甚至简单的ASCII字库表就能满足基本需求。而如果把每个字符都用编码值来存储的话,每个字符就需要3个字节(这里以Unicode字库为例),这样对于原本用仅占一个字符的ASCII编码的英语地区国家显然是一个额外成本(存储体积是原来的三倍)。算的直接一些,同样一块硬盘,用ASCII可以存1500篇文章,而用3字节Unicode序号存储只能存500篇。于是就出现了UTF-8这样的变长编码

UTF-8和Unicode的关系

Unicode就是上文中提到的编码字符集,而UTF-8就是字符编码,即Unicode规则字库的一种实现形式。随着互联网的发展,对同一字库集的要求越来越迫切,Unicode标准也就自然而然的出现。它几乎涵盖了各个国家语言可能出现的符号和文字,并将为他们编号。详见:Unicode on Wikipedia。Unicode的编号从0000开始一直到10FFFF共分为16个Plane,每个Plane中有65536个字符。而UTF-8则只实现了第一个Plane,可见UTF-8虽然是一个当今接受度最广的字符集编码,但是它并没有涵盖整个Unicode的字库,这也造成了它在某些场景下对于特殊字符的处理困难(emoji)。

java中的char 和unicode

Java中的char类型,范围0-65535,两个字节,16位,用unicode编码,字符编码UTF-8也刚好能编码65535个,char默认值是‘\u0000’,
char a = ‘0’ 和 char a = 48是一样的,char a = ‘A’ 和 char a = 65是一样的,char a = ‘\0’ 和 char a = 0是一样的,因为unicode字符集 0-127的字符和ASCII的0-127是一样的。
char a = ‘中’不会出错,表示一个字符 中 字,使用idea或者eclipse选择编码为utf-8或者是GBK都不会出错,虽然UTF-8是用3位表示一个中文,但是不影响char 定义一个中文,因为UTF-8是字符编码和字符集不一样
示例:

char m=‘a’;  ——a。

char m=‘a’+‘b’;  ——Ã。 //char类型相加,提升为int类型,输出对应的字符。注,在CMD.exe用输出结果是问题?,不同的编码输出显示不一样。Eclipse中须改成UTF-8。

int m=‘a’+‘b’;   ——195。//195没有超出int范围,直接输出195。

char m=‘a’+b;  ——报错。//因为b是一个赋值的变量。

char m=197;  ——Ã。 //输出字符编码表中对应的字符。

char m='197;  ——报错。//因为有单引号,表示是字符,只允许放单个字符。

char m=‘a’+1;  ——b。//提升为int,计算结果98对应的字符是b。

char m=‘中’+‘国’;  ——42282。

char m=‘中’+‘国’+‘国’+‘国’;  ——报错。int转char有损失。因为结果已经超出char类型的范围。

int m=‘中’+‘国’+‘国’+‘国’;  ——86820

char m=‘中’+1;  ——丮。//1是int,结果提升为int,输出对应的字符。

char m=‘中’+“国”;  ——报错。String无法转换为char。

System.out.println(‘中’+“国”);  ——中国。//没有变量附值的过程。String与任何字符用“+”相连,转换为String。
总结:
用单引号’'标识,只能放单个字符。
char+char,char+int——类型均提升为int,附值char变量后,输出字符编码表中对应的字符。

mysql中使用utf8

  • 需要存储 emoji,字符集需要采用 utf8mb4 字符集
  • mysql支持的utf8编码的最大长度为3字节,utf8是utf8mb4的子集(最大4字节)
  • 字符集不一致会导致索引失效,表结构为utf8,但是索引键为utf8mb4

iso-8859-1

也称Lantin-1,扩展ASCII:128-255,前面127和ascii一致,Lantin-1和ascii都是单字节编码,unicode前255和iso-8859-1一致

properties文件是以ISO-8859-1读取的,此文件最好只配置 英文

idea中
File -> Settings -> Editor ->General -> File Encodings 的Transparent native-to-ascii conversion 选项,建议不勾选,或者整个团队都勾选

关于Transparent native-to-ascii:
1.如果勾选了,则idea会将此properties的每个字符,变为一个unicode序列码即\uxxxx
2.或者不勾选,用转换流,字符编码为utf8读取,也可以解决乱码
3.但是使用git时,如果勾选了,则push,和 pull都是 ascii ,则显示了中文了,所以不勾选

所以建议使用2和3

为什么会出现乱码

对应到真实生活中,就好比是一个英国人为了表示祝福在纸上写了bless(编码过程)。而一个法国人拿到了这张纸,由于在法语中bless表示受伤的意思,所以认为他想表达的是受伤(解码过程)。这个就是一个现实生活中的乱码情况。在计算机科学中一样,一个用UTF-8编码后的字符,用GBK去解码。由于两个字符集的字库表不一样,同一个汉字在两个字符表的位置也不同,最终就会出现乱码。

(PS:这里科普下乱码的英文native说法是mojibake

Hex

 <dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.11</version></dependency>
package org.apachemons.codec.binary;
byte[] decodeHex(String data)
char[] encodeHex(byte[] data)Hex.decodeHex
Hex.encodeHex

我们知道在计算机中的字节共有256个组合,对应就是
ascii码,而ascii码的128~255之间的值是不可见字符(所以我们直接打开二进制文件会发现很多空白和一些乱码)。而在网络上交换数据时,比如说从A地传到B地,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。所以就先把数据先做一个Base64编码,统统变成可见字符,这样出错的可能性就大降低了。

base64参考:

16进制
在开发加密解密数据的时候碰到需要把加密好的 byte 数组转换成 String 对象用于网络传输的需求,如果把字节数组直接转换成 UTF-8 等编码方式的话肯定会存在某些编码没有对应的字符,在传输过程中会遗漏数据。这时就可以通过常用的二进制数据编码方式 Base64 编码或者 Hex 编码来实现。
因为一个字节中存在8个 bit可以表示256个字符,而 ASCII 码只能表示0-127种字符,为了能完整地表示一个字节,可以将二进制数据转换为十六进制数据的方式来实现。所以 Hex 编码也被称作为 Base16 编码,相比于原先8位表示一个字节,Hex 编码能够只用2位表示一个字节。Hex 编码最常用于二进制文件查看时展示的编码,如 Hex Fiend 就可以支持查看二进制文件。

  • Hex编码的原理就是将原来8位的二进制字节打断,分成两个4位的,并且在前面加上4个零,
  • 进行补位这样一个8位二进制字节就变成了2个8位的二进制字节,在将新得到的2个二进制字符进行16位进制转换
    得到的新的16位字符串就是Hex的值,所以 二进制的[72, 69, 88] 《hex》 484558是相等的。
  • [72, 69, 88]byte数组的二进制=‭01001000‬ ‭01000101‬ ‭01011000‬
    二进制=‭01001000‬ ‭01000101‬ ‭01011000‬ 进行hex的打断操作 ‭0100 1000‬ ‭0100 0101‬ ‭0101 1000
    在加上前面的4个零得到一个新的6个8位二进制 = 0000‭0100 00001000 ‬0000‭0100 00000101‬ 0000‭0101 00001000
    新的6个8位二进制 进行16进制转换 0000‭0100 00001000 ‬0000‭0100 00000101‬ 0000‭0101 00001000 = 484558
    总结所以说Hex编码后的二进制长度变为了原来的2倍,所以字节长度增加了一倍。

二进制流,二进制文件

  • 从本质上来说他们之间没有什么区别,因为他们在硬盘上都有一种的存放方式–二进制,
  • 但是如果要对他们有些区分的话,那可以这样理解。每个字符由一个或多个字节组成,每个字节都是用的-128—127之间的部分数值来表示的,也就是说,-128——127之间还有一些数据没有对应任何字符的任何字节。
  • 如果一个文件中的每个字节的内容都是可以表示成字符的数据,我们就可以称这个文件为文本文件,可见,文本文件只是二进制文件中的一种特例,为了与文本文件相区别,人们又把除了文本文件以外的文件称为二进制文件,由于很难严格区分文本文件和二进制文件的概念,所以我们可以简单地认为,如果一个文件专门用于存储文本字符的数据,没有包含字符以外的其他数据,我们就称之为文本文件,除此之外的文件就是二进制文件。

为什么用二进制文件

为什么要使用二进制文件。原因大概有三个:

  • 第一是二进制文件比较节约空间
  • 第二个原因是,内存中参加计算的数据都是用二进制无格式储存起来的,因此,使用二进制储存到文件就更快捷。如果储存为文本文件,则需要一个转换的过程。在数据量很大的时候,两者就会有明显的速度差别了。
  • 第三,就是一些比较精确的数据,使用二进制储存不会造成有效位的丢失。

为什么http是文本传输协议还能传图片和文件?

参考:
HTTP传输的是字符串,到了TCP层以二进制传输。
因为其增加了MIME。http/1.0之后版本在传输非文本文件的时候,比如图片,其实是使用MIME转换成ASCII传输的
具体参考 MIME和计算机网络

本文标签: 字符集与字符编码,java中的char和unicode