一文解开java中字符串编码的小秘密(干货)

简介

在本文中你将了解到Unicode和UTF-8,UTF-16,UTF-32的关系,同时你还会了解变种UTF-8,并且探讨一下UTF-8和变种UTF-8在java中的应用。

一起来看看吧。

Unicode的发展史

在很久很久以前,西方世界出现了一种叫做计算机的高科技产品。

初代计算机只能做些简单的算数运算,还要使用人工打孔的程序才能运行,不过随着时间的推移,计算机的体积越来越小,计算能力越来越强,打孔已经不存在了,变成了人工编写的计算机语言。

一切都在变化,唯有一件事情没有变化。这件事件就是计算机和编程语言只流传在西方。而西方日常交流使用26个字母加有限的标点符号就够了。

最初的计算机存储可以是非常昂贵的,我们用一个字节也就是8bit来存储所有能够用到的字符,除了最开始的1bit不用以外,总共有128中选择,装26个小写+26个大写字母和其他的一些标点符号之类的完全够用了。

这就是最初的ASCII编码,也叫做美国信息交换标准代码(American Standard Code for Information Interchange)。

后面计算机传到了全球,人们才发现好像之前的ASCII编码不够用了,比如中文中常用的汉字就有4千多个,怎么办呢?

没关系,将ASCII编码本地化,叫做ANSI编码。1个字节不够用就用2个字节嘛,路是人走出来的,编码也是为人来服务的。于是产生了各种如GB2312, BIG5, JIS等各自的编码标准。这些编码虽然与ASCII编码兼容,但是相互之间却并不兼容。

这严重的影响了国际化的进程,这样还怎么去实现同一个地球,同一片家园的梦想?

于是国际组织出手了,制定了UNICODE字符集,为所有语言的所有字符都定义了一个唯一的编码,unicode的字符集是从U+0000到U+10FFFF这么多个编码。

那么unicode和UTF-8,UTF-16,UTF-32有什么关系呢?

unicode字符集最后是要存储到文件或者内存里面的,直接存储的话,空间占用太大。那怎么存呢?使用固定的1个字节,2个字节还是用变长的字节呢?于是我们根据编码方式的不同,分成了UTF-8,UTF-16,UTF-32等多种编码方式。

其中UTF-8是一种变长的编码方案,它使用1-4个字节来存储。UTF-16使用2个或者4个字节来存储,JDK9之后的String的底层编码方式变成了两种:LATIN1和UTF16。

而UTF-32是使用4个字节来存储。这三种编码方式中,只有UTF-8是兼容ASCII的,这也是为什么国际上UTF-8编码方式比较通用的原因(毕竟计算机技术都是西方人搞出来的)。

Unicode详解

知道了Unicode的发展史之后,接下来我们详解讲解一下Unicode到底是怎么编码的。

Unicode标准从1991年发布1.0版本,已经发展到2020年3月最新的13.0版本。

Unicode能够表示的字符串范围是0到10FFFF,表示为U+0000到U+10FFFF。

其中U+D800到U+DFFF的这些字符是预留给UTF-16使用的,所以Unicode的实际表示字符个数是216 − 211 + 220 = 1,112,064个。

我们将Unicode的这些字符集分成17个平面,各个平面的分布图如下:

以Plan 0为例,Basic Multilingual Plane (BMP)基本上包含了大部分常用的字符,下图展示了BMP中所表示的对应字符:

上面我们提到了U+D800到U+DFFF是UTF-16的保留字符。其中高位U+D800–U+DBFF和低位U+DC00–U+DFFF是作为一对16bits来对非BMP的字符进行UTF-16编码。单独的一个16bits是无意义的。

UTF-8

UTF-8是用1到4个字节来表示所有的1,112,064个Unicode字符。所以UTF-8是一种变长的编码方式。

UTF-8目前是Web中最常见的编码方式,我们看下UTF-8怎么对Unicode进行编码:

最开始的1个字节可以表示128个ASCII字符,所以UTF-8是和ASCII兼容的。

接下来的1,920个字符需要两个字节进行编码,涵盖了几乎所有拉丁字母字母表的其余部分,以及希腊语,西里尔字母,科普特语,亚美尼亚语,希伯来语,阿拉伯语,叙利亚语,Thaana和N'Ko字母,以及组合变音符号标记。BMP中的其余部分中的字符需要三个字节,其中几乎包含了所有常用字符,包括大多数中文,日文和韩文字符。Unicode中其他平面中的字符需要四个字节,其中包括不太常见的CJK字符,各种历史脚本,数学符号和表情符号(象形符号)。

下面是一个具体的UTF-8编码的例子:

UTF-16

UTF-16也是一种变长的编码方式,UTF-16使用的是1个到2个16bits来表示相应的字符。

UTF-16主要在Microsoft Windows, Java 和 JavaScript/ECMAScript内部使用。

不过UTF-16在web上的使用率并不高。

接下来,我们看一下UTF-16到底是怎么进行编码的。

首先:U+0000 to U+D7FF 和 U+E000 to U+FFFF,这个范围的字符,直接是用1个16bits来表示的,非常的直观。

接着是:U+010000 to U+10FFFF

这个范围的字符,首先减去0x10000,变成20bits表示的0x00000–0xFFFFF。

然后高10bits位的0x000–0x3FF加上0xD800,变成了0xD800–0xDBFF,使用1个16bits来表示。

低10bits的0x000–0x3FF加上0xDC00,变成了0xDC00–0xDFFF,使用1个16bits来表示。

U' = yyyyyyyyyyxxxxxxxxxx // U - 0x10000

W1 = 110110yyyyyyyyyy // 0xD800 + yyyyyyyyyy

W2 = 110111xxxxxxxxxx // 0xDC00 + xxxxxxxxxx

这也是为什么在Unicode中0xD800–0xDFFF是UTF-16保留字符的原因。

下面是一个UTF-16编码的例子:

UTF-32

UTF-32是固定长度的编码,每一个字符都需要使用1个32bits来表示。

因为是32bits,所以UTF-32可以直接用来表示Unicode字符,缺点就是UTF-32占用的空间太大,所以一般来说很少有系统使用UTF-32.

Null-terminated string 和变种UTF-8

在C语言中,一个string是以null character ('\0')NUL结束的。

所以在这种字符中,0x00是不能存储在String中间的。那么如果我们真的想要存储0x00该怎么办呢?

我们可以使用变种UTF-8编码。

在变种UTF-8中,null character (U+0000) 是使用两个字节的:11000000 10000000 来表示的。

所以变种UTF-8可以表示所有的Unicode字符,包括null character U+0000。

通常来说,在java中,InputStreamReader 和 OutputStreamWriter 默认使用的是标准的UTF-8编码,但是在对象序列化和DataInput,DataOutput,JNI和class文件中的字符串常量都是使用的变种UTF-8来表示的。

补充知识:Java基础之字符串的编码(Encode)和解码(Decode)

废话不多说,看代码~

package newFeatures8;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;

/*
 * 编码(由看得懂到看不懂):字符串变字节数组
 * 解码(由看不懂到看得懂):字符数组变字符串
 * String--》byte[];//str.getBytes();//str.getBytes(String CharsetName);
 * byte[]--》String;//new String(byte[] bytes)//new String(byte[] bytes,String CharsetName);
 */

public class Practice {

	public static void main(String[] args) {
		try {
			 String s="你好";
			 //ISO-8859-1 根本就不识别中文

			 // byte[] bytes=s.getBytes("gbk");
			 // System.out.println(Arrays.toString(bytes));//[-60, -29, -70, -61]

			 //使用utf-8 编码每个字符占3个字节
			 //byte[] bytes=s.getBytes("utf-8");
			 // System.out.println(Arrays.toString(bytes));//[-28, -67, -96, -27, -91, -67]

			 // String s1=new String(s.getBytes("utf-8"), "gbk");//浣犲ソ

			 // String s1=new String(s.getBytes("gbk"), "utf-8");//???

			 //当网页已经出现乱码,而使用的Tomcat服务器,Tomcat服务器使用的是ISO-8859-1 只需要再编码解码即可
			  String s1=new String(s.getBytes("ISO-8859-1"), "utf-8");
			  System.out.println(s1);
			  //一般要养成一个习惯:就是全部用utf-8
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}

	}
}
package newFeatures8;
import java.io.UnsupportedEncodingException;
public class Practice {
	public static void main(String[] args) throws UnsupportedEncodingException {
		getLowest8Bit();
	}
	/*
	 * 通过研究发现:当往记事本里写入"联通"两字时,保存后打开,发现出现乱码
	 * 原因是:当你写入中文时:记事本使用的是GBK(按照一个字符两个字节)编码
	 * ,当你打开记事本时,使用的是UTF-8(按照一个字符3个字节)解码
	 * 如何解决:只要在联通前加个汉字即可,不能是字母
	 *
	 * "联通"二字比较特殊
	 * 其二进制数的最低8位刚好符合UTF-8的解码格式
	 */
	public static void getLowest8Bit() throws UnsupportedEncodingException{
		String s="联通";
		byte[] bytes=s.getBytes("gbk");
		for (byte b : bytes) {
			//System.out.println(Integer.toBinaryString(b));
			/*
			  11111111111111111111111111000001
				11111111111111111111111110101010
				11111111111111111111111111001101
				11111111111111111111111110101000
			 */
			//通过使用 与上 &0xff 来获取其最低最低8位 0xff=255
			System.out.println(Integer.toBinaryString(b&0xff));
			/*
			 * 11000001
				10101010
				11001101
				10101000
			 */
			//匹配到了utf-8 的标志位
			//一个字节 标志位0打头
			//两个字节 :第一个字节110打头,第二个字节10打头
			//三个字节:第一个字节1110打头,第二个字节10打头,第三个字节10打头
		}
	}

}

以上这篇一文解开java中字符串编码的小秘密(干货)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • java转换字符串编码格式的方法

    java转换字符串编码格式 (解码错误,重新解码) 字符集概念:规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系. 我们在计算机屏幕上看到的是实体化的文字,而在计算机存储介质中存放的实际是二进制的比特流. 乱码场景(纯属瞎掰): 1) 前台输入utf-8编码的一串汉字(string1). (页面编码为utf-8, 在内存中会将这串汉字以utf-8编码为对应的二进制流存储) 2) 这串汉字(string1)的二进制流在经过http协议传输到后台时,这段

  • Java Base64位编码与String字符串的相互转换,Base64与Bitmap的相互转换实例代码

    首先是网上大神给的类 package com.duanlian.daimengmusic.utils; public final class Base64Util { private static final int BASELENGTH = 128; private static final int LOOKUPLENGTH = 64; private static final int TWENTYFOURBITGROUP = 24; private static final int EIGH

  • Java反转字符串和相关字符编码的问题解决

    复制代码 代码如下: public String reverse(char[] value){       for (int i = (value.length - 1) >> 1; i >= 0; i--){           char temp = value[i];           value[i] = value[value.length - 1 - i];           value[value.length - 1 - i] = temp;       }     

  • Java JDK1.7对字符串的BASE64编码解码方法

    如下所示: package cn.itcast; import java.io.IOException; import java.io.UnsupportedEncodingException; import org.junit.Test; import sun.misc.BASE64Decoder; /* * @author soto * BASE64编码 解码 * */ public class Demo1 { @Test public void fun1() throws IOExcept

  • JS实现对中文字符串进行utf-8的Base64编码的方法(使其与Java编码相同)

    本文实例讲述了JS实现对中文字符串进行utf-8的Base64编码的方法.分享给大家供大家参考,具体如下: 要进行编码的字符串:"select 用户名 from 用户" 使用JAVA进行编码,Java程序: String sql = "select 用户名 from 用户"; String encodeStr = new String(Base64.encode(sql.getBytes("UTF-8"))); // 编码 System.out.

  • 一文解开java中字符串编码的小秘密(干货)

    简介 在本文中你将了解到Unicode和UTF-8,UTF-16,UTF-32的关系,同时你还会了解变种UTF-8,并且探讨一下UTF-8和变种UTF-8在java中的应用. 一起来看看吧. Unicode的发展史 在很久很久以前,西方世界出现了一种叫做计算机的高科技产品. 初代计算机只能做些简单的算数运算,还要使用人工打孔的程序才能运行,不过随着时间的推移,计算机的体积越来越小,计算能力越来越强,打孔已经不存在了,变成了人工编写的计算机语言. 一切都在变化,唯有一件事情没有变化.这件事件就是计

  • 一文详解Java中字符串的基本操作

    目录 一.遍历字符串案例 二.统计字符次数案例 三.字符串拼接案例 四.字符串反转案例 五.帮助文档查看String常用方法 一.遍历字符串案例 需求:键盘录入一个字符串,使用程序实现在控制台遍历该字符串 思路: 1.键盘录入一个字符串,用 Scanner 实现 2.遍历字符串,首先要能够获取到字符串中的每一个字符 public char charAt(int index):返回指定索引处的char值,字符串的索引也是从0开始的 3.遍历字符串,其次要能够获取到字符串的长度 public int

  • Java中字符串中连续相同字符去重方法

    最近参加了一个面试,问到了如何在一个字符串中连续相同字符去重,想了想用正则表达式应该可以轻松实现.可是由于长时间没有编码了,而且由于原先的工作用到的比较少.具体的自己就不会写正则表达式用到的类名什么的了.总之就是面试没有过了. 回来再网上搜了搜,本来以为可以很容易找到相应的内容,可是找了半天没有找到我想要的结果.后来在某个相似问题求助中看到了相应答案,不过还是有所区别,根据该问题的解决思路,最后实现了. 代码如下: public class Test { public static void m

  • java中Base64编码原理实例讲解

    什么是 Base64 编码 Base64 编码是最常见的编码方式,基于 64 个可打印字符来表示任意二进制数据的方法,是从二进制转换到可见字符的过程. 使用场景 数据加密或签名通过 Base64 转换为字符串存储或传输. 不能传输文件的网络环境可以转换 Base64 进行网络传输. 在文本资源(如 HTML 和 CSS文件)中嵌入图片文件或其他二进制资源. 在 URL.网页中传输少量二进制数据等等. Base64 编码原理 原理是把每 3 个字节(每个字节为 8 位, 3 个字节为 24 位)重

  • 一文了解Java中枚举的使用

    目录 概述 枚举介绍和使用 枚举的本质 枚举常见用途 枚举创建单例 枚举抽象方法 概述 Java中枚举,大家在项目中经常使用吧,主要用来定义一些固定值,在一个有限的集合内,比如在表示一周的某一天,一年中的四季等.那你了解枚举的本质吗?了解枚举的一些常见用法吗? 枚举介绍和使用 枚举主要用来定义一个有限集合内的固定值. 枚举定义方式如下: // 简单的定义 enum WeekEnum { MONDAY, TUESDAY } // 有属性的定义 enum StatusEnum { ENABLE("1

  • 基于Java中字符串内存位置详解

    前言 之前写过一篇关于JVM内存区域划分的文章,但是昨天接到蚂蚁金服的面试,问到JVM相关的内容,解释一下JVM的内存区域划分,这部分答得还不错,但是后来又问了Java里面String存放的位置,之前只记得String是一个不变的量,应该是要存放在常量池里面的,但是后来问到new一个String出来应该是放到哪里的,这个应该是放到堆里面的,后来又问到String的引用是放在什么地方的,当时傻逼的说也是放在堆里面的,现在总结一下:基本类型的变量数据和对象的引用都是放在栈里面的,对象本身放在堆里面,

  • java中字符串转整数及MyAtoi方法的实现

    java中字符串转整数及MyAtoi方法的实现 该题虽然和我们正常使用的字符串转整数的API中函数不一致,但是通过增加了很多额外的边界或者异常处理,可以锻炼算法思维的敏锐性和处理边界异常等问题的能力. 思路:字符串题一般考查的都是边界条件.特殊情况的处理.所以遇到此题一定要问清楚各种条件下的输入输出应该是什么样的. 这里已知的特殊情况有: 能够排除首部的空格,从第一个非空字符开始计算 允许数字以正负号(+-)开头 遇到非法字符便停止转换,返回当前已经转换的值,如果开头就是非法字符则返回0 在转换

  • Java中字符串的一些常见方法分享

    1.Java中字符串的一些常见方法 复制代码 代码如下: /** *  */package com.you.model; /** * @author Administrator * @date 2014-02-24 */public class Replace { /**  * @param args  */ public static void main(String[] args)  {  /**   * 原字符串   */  String str = "78454545855ksdjnf

  • 浅谈java中字符串数组、字符串、整形之间的转换

    字符串数组转字符串(只能通过for循环): String[] str = {"abc", "bcd", "def"}; StringBuffer sB = new StringBuffer(); for (int i = 0; i < str.length;i++) { sB.append(str[i]); } String s = sB.toString(); 字符数组转字符串可以通过下面的方式: char[] data = {"

  • 基于Java中字符串indexof() 的使用方法

    Java中字符串中子串的查找共有四种方法(indexof()) indexOf 方法返回一个整数值,指出 String 对象内子字符串的开始位置.如果没有找到子字符串,则返回-1. 如果 startindex 是负数,则 startindex 被当作零.如果它比最大的字符位置索引还大,则它被当作最大的可能索引. Java中字符串中子串的查找共有四种方法,如下: 1.int indexOf(String str) :返回第一次出现的指定子字符串在此字符串中的索引. 2.int indexOf(St

随机推荐