java中的字符集和编码

时间:2021-06-18 10:02:00

前言

上次对计算机中的“字符集”和“编码”分别进行了总结,并指出二者之间的区别,不要搞混了,不清楚的再回到上一章看一下。今天再总结下java中是如何使用字符集(主要是Unicode字符集,其他常用字符集都只有一种编码规则),以及是如何使用utf-8、utf-16、utf-32对Unicode字符集进行编码的。

java中的char类型

java中的char类型占用两个字节、用于定义字符,这些字符只覆盖了Unicode字符集中的第0个平面中定义的符号(该平面中定义的符号 都是地球人最常用的65536个),也就是说其他16个平面中的符号是没办法有java的char类型表示的。

  1. char c0 = 'A';
  2. char c1='天';
  3. char c2='星';
  4. char c3 = 'XXX';//编译错误

提示:这个字iteye无法识别,导致文章提交被截断,文中所有’XXX’都是表示这个字。

前三个char类型赋值没有问题,第四个赋值直接编译错误。’XXX’这个字的Unicode码值是:10 10001000 10111011,可以看出使用两个字节是放不下的。如果使用utf-16进行编码,编码后的二进制值如下(需要4个字节,编码格式可以参考上一篇文章):

1101100 00110001 01101110 010111011

这也就是为什么不能把'XXX'赋值给java char类型的原因。

Java中字符串转字节

Java中的字符串(String)在网络传输或者存储硬盘的真实内容是 通过字符集编码转换后的二进制数字。在String类中定义了几个getBytes重载方法,来获取字符串对应的字节数组,并且默认使用的是Unicode字符集的utf-8编码:测试代码如下:

  1. byte[] bytes = "XXX".getBytes();
  2. System.out.println("默认编码:"+new BigInteger(1,bytes).toString(2));
  3. byte[] bytes3 = "XXX".getBytes("utf-8");
  4. System.out.println("使用utf-8编码:"+new BigInteger(1,bytes3).toString(2));

打印内容为:

  1. 默认编码:11110000101010001010001010111011
  2. 使用utf-8编码:11110000101010001010001010111011

可以发现使用不带参数和带"utf-8"参数的结果是完全一致的。即可说明:getBytes不带参数的默认方法使用的是Unicode字符集的utf-8编码规则进行编码的。"XXX"不是常用的汉字,这里使用了4字节,常用汉字使用utf-8编码一般三个字节。

在上一章中讲到过utf-16是使用2或者4字节进行存储,我们来看下在java语言中的表现:

  1. byte[] bytes1 = "XXX".getBytes("Unicode");
  2. System.out.println("使用Unicde字符集的默认编码:"+new BigInteger(1,bytes1).toString(2));
  3. byte[] bytes2 = "XXX".getBytes("utf-16");
  4. System.out.println("使用utf-16编码:"+new BigInteger(1,bytes2).toString(2));
  5. byte[] bytes4 = "XXX".getBytes("ASCII");
  6. System.out.println("使用ASCII字符集:"+new BigInteger(1,bytes4).toString(2));

打印结果为:

  1. 使用Unicde字符集的默认编码:111111101111111111011000011000101101110010111011
  2. 使用utf-16编码:111111101111111111011000011000101101110010111011
  3. 使用ASCII字符集:111111

注意这里区别:

调用getBytes("Unicode")方法 表示使用Unicode字符集的默认编码方式进行编码。这个默认编码方式一般由操作系统指定,我的是win7,显示跟utf-8编码相同;

调用getBytes("utf-16")方法 表示使用默认Unicode字符集的utf-16编码方式进行编码。

另外我们上一章说过,utf-16使用2或者4个字节存储,"XXX"不在第0平面,应该占用4个字节。但实际打印出来的是11111110 11111111 11011000 01100010 11011100 10111011,一共6个字节,怎么多了两个字节呢?我们还可以发现无论是什么字符串,通过调用getBytes("utf-16")方法,打印出来的前面都会固定多两个字节:11111110 11111111,换算成16进制就是0x FEFF。这里有一个大端序和小端序的概念。

大端序和小端序

前一篇文章也提到过utf-8、utf-16、utf-32有一个区别就是一次最少读入的字节数,utf-8是一次最少读入一个字节(8个bit),utf-16是2两字节,utf-32是4个字节。其中utf-16和utf-32一次需要读入多个字节,根据读取顺序的不同,分为大端序和小端序。

大端序:简单的理解就是从左往右依次读入两个(utf-16)或者4个(utf-32)字节

小端序:简单的理解就是从右往做反向依次读入两个(utf-16)或者4个(utf-32)字节

大端序编码:UTF-16Be、UTF-32Be;小端序:UTF-16Le、UTF-32Le;另外在java中还可以使使用UnicodeBig、UnicodeLittle,表示使用默认的UTF-16大、小端序编码,与UTF-16Le、UTF-32Le基本等效,区别就是UnicodeBig、UnicodeLittle的前面会自动加上两字节,用于表示大小端序。

UnicodeBig自动在编码后的最前面加上:1111111011111111 换成16进制为 FEFF;

UnicodeLittle自动在编码后的最前面加上:1111111111111110 换成16进制为FFFE。

测试代码如下:

  1. byte[] bytes2 = "XXX".getBytes("utf-16");
  2. System.out.println("使用utf-16编码:"+new BigInteger(1,bytes2).toString(2));
  3. byte[] bytes6 = "XXX".getBytes("utf-16le");
  4. System.out.println("使用utf-16小端序编码:"+new BigInteger(1,bytes6).toString(2));
  5. byte[] bytes7 = "XXX".getBytes("utf-16be");
  6. System.out.println("使用utf-16大端序编码:"+new BigInteger(1,bytes7).toString(2));
  7. byte[] bytesx = "XXX".getBytes("utf-32be");
  8. System.out.println("使用utf-32大端序编码:"+new BigInteger(1,bytesx).toString(2));
  9. byte[] bytesy = "XXX".getBytes("utf-32le");
  10. System.out.println("使用utf-32小端序编码:"+new BigInteger(1,bytesy).toString(2));
  11. byte[] bytesz = "XXX".getBytes("utf-32");
  12. System.out.println("使用utf-32编码:"+new BigInteger(1,bytesz).toString(2));
  13. byte[] bytes8 = "XXX".getBytes("UnicodeBig");
  14. System.out.println("使用Unicode大端序编码:"+new BigInteger(1,bytes8).toString(2));
  15. byte[] bytes9 = "XXX".getBytes("UnicodeLittle");
  16. System.out.println("使用Unicode小端序编码:"+new BigInteger(1,bytes9).toString(2));

运行结果为:

  1. 使用utf-16编码:111111101111111111011000011000101101110010111011
  2. 使用utf-16小端序编码:1100010110110001011101111011100
  3. 使用utf-16大端序编码:11011000011000101101110010111011
  4. 使用utf-32大端序编码:101000100010111011
  5. 使用utf-32小端序编码:10111011100010000000001000000000
  6. 使用utf-32编码:101000100010111011
  7. 使用Unicode大端序编码:111111101111111111011000011000101101110010111011
  8. 使用Unicode小端序编码:111111111111111001100010110110001011101111011100

用BigInteger没有打印出完整的二进制,它把前面是0的全部去掉了。另外我们可以发现在在不指定大小端序的情况下默认是使用的大端序,在上一节中直接使用getBytes("utf-16")或者getBytes("Unicode")时就可以看出,前面多的两个字节刚好就是用来表示默认是“大端序”FEFF。

为什么要有大小端序呢,原因很简单:不同的操作系统的不同实现罢了,有些操作系统默认是大端序(比如我的win7下),有些默认又是小端序。所以最好指定清楚,否则容易引起乱码。

另外在window下utf-8还有带与不带BOM的区别,也就是在字符串的最前面会多出FEFF,在其他系统上显示就会有乱码,在linux操作系统下很少见到使用,这里就不深入了。

Java解码--字节数组转字符串

Java中解码,直接调用String的带字节数组的构造方法解码,生成人类能识别的符号。如果人类不能识别就是我们所谓的乱码。同时该构造方法还有另外一个参数,表示使用的字符集编码,看一个例子:

  1. byte[] b16 = "天星".getBytes("utf-16");
  2. String n8=new String(b16,"utf-8");
  3. System.out.println("使用utf-8解码 utf-16编码的字符串:"+n8);
  4. byte[] b8 = n8.getBytes("utf-8");
  5. String n16 = new String(b8,"utf-16");
  6. System.out.println("还原:"+n16);

这个例子首先使用utf-16编码对字符串进行编码;

然后使用了utf-8编码进行解码,此时由于编解码不一致,发现打印出来有乱码;

接着试图把这个乱码还原回去,再使用utf-16进行解码。

也许你期望的应该是没有乱码,可以还原,但真实的打印结果如下:

  1. 使用utf-8解码 utf-16编码的字符串:��Y)f­
  2. 还原:뷯뾽天星

发现还是有乱码,没有真正还原。这是为什么呢?这其实就是默认大端序会加两个字节引起的的问题,我们把程序改下,指定在16进制时使用大端序:

  1. byte[] b16 = "天星".getBytes("utf-16Be");
  2. String n8=new String(b16,"utf-8");
  3. System.out.println("使用utf-8解码 utf-16编码的字符串:"+n8);
  4. byte[] b8 = n8.getBytes("utf-8");
  5. String n16 = new String(b8,"utf-16Be");
  6. System.out.println("还原:"+n16);

运行结果:

  1. 使用utf-8解码 utf-16编码的字符串:Y)f­
  2. 还原:天星

发现已经神奇的还原了。

导致乱码的根本原因是编码和解码使用了不同的字符集,或者相同的字符集下使用了不同的编码规则进行编解码。但反过来确不成立,也就是说在有些情况下即便是使用了不同的字符集或编码规则进行编解码,也不会出现乱码,比如我上一个例子。这就要求我们对字符集的编码规则要相当熟悉,并能灵活运用。

理论上在编码时只要可以保证数据不丢失,都可以先还原回去,再使用正确的字符集编码进行解码,可以得到正常的结果。比如下面的例子:

  1. byte[] b16 = "天星".getBytes("ASCII");
  2. String n8=new String(b16,"utf-8");
  3. System.out.println("使用utf-8解码 utf-16编码的字符串:"+n8);
  4. byte[] b8 = n8.getBytes("utf-8");
  5. String n16 = new String(b8,"ASCII");
  6. System.out.println("还原:"+n16);

运行结果是乱码,主要原因就是在"天星".getBytes("ASCII")这一步产生了数据丢失,后面再牛逼的人也无力回天了。但如果把“天星”改成“abc”是可以还原的,因为这些字符本身就是ASCII中定义的字符。这也算是英文的优势吧。

总结

关于java中的字符集和编码就总结到这里,这次总结的起因是要分析 恶意用户的恶意请求参数。他使用了utf-16进行编码,而我们日志服务器默认是使用utf-8进行解码,从而导致乱码。但如果熟悉了我上面讲解的内容,相信你也可以把真实的内容还原回来。当然中间还使用另外一些加密手段,这里就不再一一详解了。

提示:这个字iteye无法识别,导致文章提交被截断,文中所有’XXX’都是表示这个字。

java中的字符集和编码的更多相关文章

  1. WEB开发中的字符集和编码

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...

  2. Ruby:字符集和编码学习总结

    背景 Ruby直到1.9版本才很好的支持了多字节编码,本文简单总结了今天学习的关于Ruby编码方面的知识. 字符串可以使用不同的编码 在.NET中字符串的编码是一致的,Ruby允许字符串有不同的编码, ...

  3. .NET:字符集和编码学习总结

    背景 一直没有深入的学习字符集和编码的知识(现在也没有深入),今天查阅了一些资料,弄明白了一些事情,本文就简单记录一下. 字符集和编码 字符集是指一些符号组成的集合,编码是对指定字符集如何表示为字节的 ...

  4. Java中面向字符的输入流

    Java中面向字符的输入流 2016-12-04 Java程序员联盟 Java程序员联盟 Java程序员联盟 微信号 javalm 功能介绍 莫道君行早,更有早行人 全心敲代码,天道自酬勤 字符流是针 ...

  5. 在Java中如何进行BASE64编码和解码

    在Java中如何进行BASE64编码和解码 //在Java中如何进行BASE64编码和解码 package me.xzh.study.sun.misc.BASE64; import sun.misc. ...

  6. java中的字符编码方式

    1. 问题由来 面试的时候被问到了各种编码方式的区别,结果一脸懵逼,这个地方集中学习一下. 2. 几种字符编码的方式 1. ASCII码 我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符 ...

  7. java中的字符,字节和编码

    1. 编码问题的由来,相关概念的理解 1.1 字符与编码的发展 从计算机对多国语言的支持角度看,大致可以分为三个阶段:   系统内码 说明 系统 阶段一 ASCII 计算机刚开始只支持英语,其它语言不 ...

  8. java中处理字符编码(网页与数据库)(转)

    首先声明一下,此文章时从网上转载的.如下的某些方法是确实管用,但是从中发现了有一点不足,就是原文笔者没考虑使用不同Web Server时出现的情况,比如文章里我用红色字体画出来的部分代码在Tomcat ...

  9. mysql中对字符集和校对规则的认识

    字符集:指符号和字符编码的集合.校对规则:比较字符编码的方式.GBK2312:主要包括简体中文字符及常用符号,对于中文字符采用双字节编码的格式,也就是说一个汉字字符在存储占两个字节.GBK:包括有中. ...

随机推荐

  1. Collections的排序之一(Java)

    package home.collection.arr; import java.util.ArrayList; import java.util.Collections; import java.u ...

  2. Microsoft Dynamics CRM 2011 相关-摘自网络

    Microsoft Dynamics CRM Server 2011硬件需求: 组件 *最低要求 *推荐配置 处理器 x64 体系结构或兼容的双核 1.5 GHz 处理器 四核 x64 体系结构 2 ...

  3. ConcurrentHashMap总结

    线程不安全的HashMap 因为多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap,如以下代码   final HashM ...

  4. Linux入门基础知识

    注:内容系兄弟连Linux教程(百度传课:史上最牛的Linux视频教程)的学习笔记. Linux入门基础知识 1. Unix和Linux发展历史 二者就像父子关系,当然Unix是老爹.1965年,MI ...

  5. SpringMVC---CookieValue

    配置文件承接一二章 @CookieValue的作用 用来获取Cookie中的值 1.value:参数名称 2.required:是否必须 3.defaultValue:默认值 原网址:https:// ...

  6. python记录_day11 闭包 迭代器

    一.第一类对象: 函数名是一个变量,可以当普通变量使用,但它又是一个特殊的变量,与括号配合可以执行函数. 函数名的运用 1.单独打印是一个内存地址 2.可以给其他变量赋值 3.可以作为容器类变量的元素 ...

  7. 2.22 JS处理富文本

    2.22 JS处理富文本 前言    <富文本>这篇解决了富文本上iframe问题,其实没什么特别之处,主要是iframe的切换,本篇讲解通过js的方法处理富文本上iframe的问题一.加 ...

  8. 【Unity】第10章 Mecanim动画系统

    分类:Unity.C#.VS2015 创建日期:2016-05-02 一.简介 Unity提供了两种动画系统:一种是早期版本提供的旧版(Legacy)动画系统,旧版本(Legacy)以后将逐步被淘汰掉 ...

  9. Informatica bulk和normal模式

    转载:http://bestxiaok.iteye.com/blog/1107612 Bulk 方式进行目标数据的Load,是Informatica提供的一种高性能的Load数据方式.它利用数据库底层 ...

  10. C&num;中如果类的扩展方法和类本身的方法签名相同,那么会优先调用类本身的方法

    新建一个.NET Core项目,假如我们有如下代码: using System; namespace MethodOverload { static class DemoExtension { pub ...