Java基础进阶 转换流、编码、解码、乱码

1、转换流(掌握)

我们学习字符流的时候知道字符流:它的底层是字节流和编码表。
需求:在硬盘上新建一个文本文件D:\out.txt,输入”你好”两个汉字,并以UTF-8编码保存,使用程序读取文件中的数据并打印到控制台上。
代码如下:
分析和步骤:
1)创建输入流FileReader类的对象fr,D:\out.txt作为参数;
2)定义字符数组,数组名是ch,长度是1024;
3)定义一个变量len=0记录着读取的字符个数;
4)使用循环来控制次数并输出读取到的数据;
5)关闭资源;

/*
 * 转换流的演示
 */
public class ExchangeDemo {
    public static void main(String[] args) throws IOException {
        //创建字符输入流的对象
        FileReader fr = new FileReader("D:\\out.txt");
        //定义数组
        char[] ch=new char[1024];
        int len=0;
        while((len=fr.read(ch))!=-1)
        {
            System.out.println(new String(ch,0,len));
        }
        //关闭资源
        fr.close();
    }
}

结果如下:


1.png

问题分析:
硬盘上有个记事本文件中保存的数据是以UTF-8编码保存的,现在我们使用的FileReader在读取这个记事本中的数据,结果记事本中保存的“你好”,但使用程序读取到的“浣犲ソ”,数据读取错误了。
错误原因:
出现上述的错误数据的原因是记事本文件中保存的数据使用的编码表和我们程序中读取数据使用的编码表不一致。导致数据错误了。

而我们知道字符流在读取数据的时候,是先从底层读取字节数据,然后再结合编码表查到对应的字符数据。现在我们希望读取到正确的数据,必须使用和保存时一致编码表。不能再使用FileReader进行读取,因为FileReader读取数据底层使用的是默认的编码表GBK来读取数据的,而这里我们需要在读取数据的时候指定编码表。

那么既然不能使用FileReader来根据默认编码表进行读取数据,那么怎样通过指定的编码表来进行读取数据呢?

Java提供了一个转换流InputStreamReader可以在读取数据的时候指定编码表,然后把字节数据根据指定编码表转成字符数据。

1.1、转换流介绍

转换流:它的主要功能就是进行字节数据和字符数据之间的转换的。转换流它就是用来转换的,它不能和底层的文件进行关联,也就是说转换流不能去读写文件中的数据。读取底层的字符数据还是得需要我们之前讲过的字符流FileReader。向硬盘中的文件中写数据得需要我们之前讲过的FileWriter类。

Java中提供了2个转换流对象:
InputStreamReader:输入流(硬盘文件数据------》内存),它的功能是把读取到的字节(硬盘上的数据都是字节数据)转换转成字符数据。字节转字符输入转换流。
OutputStreamWriter:输出流(内存---->硬盘文件),它的功能是把字符数据转成字节数据并写到硬盘上指定的文件中。字符转字节输出转换流。

转换流的执行原理如下图所示:


2.png

1.2、字节数据转成字符数据的输入转换流

3.png

用来把字节数据转为字符数据的转换流。
构造函数如下图所示:


4.png

InputStreamReader构造函数中的字节流是负责从文件中读取字节数据,然后把字节数据交给转换流。

为了不出现上述乱码的结果,我们使用转换流来实现,根据指定的编码来读取文件中的数据,

代码如下所示:
分析和步骤:
1)创建字节输入流FileInputStream 类的对象fis,D:\out.txt作为参数;
2)创建字节转成字符的输入转换流InputStreamReader类的对象isr,并指定编码方式是utf-8;
3)创建字符数组ch,数组长度是1024,并同时定义一个变量len来记录读取字符的个数;
4)使用while循环控制循环次数来读取字符并输出内容;
5)关闭转换流资源;

/*
 * 字节转成字符的输入转换流
 */
public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
        //创建字节输入流对象
        FileInputStream fis = new FileInputStream("D:\\out.txt");
        //创建字节数据转成字符数据的输入转换流 如果这里不指定编码表,那么和FileReader一样
//      InputStreamReader isr = new InputStreamReader(fis,"gbk");
        InputStreamReader isr = new InputStreamReader(fis,"utf-8");
        //创建数组
        char[] ch=new char[1024];
        int len=0;
        while((len=isr.read(ch))!=-1)
        {
            System.out.println(new String(ch,0,len));
        }
        //关闭资源
        isr.close();//关闭转换流在底层同时就会关闭字节流
    }
}

1.3、字符数据转成字节数据的输出转换流

5.png

说明:用来把字符数据转为字节数据的转换流。

构造函数如下所示:


6.png

OutputStreamWriter:构造函数中接收的这个OutputStream是负责把OutputStreamWriter转后的字节数据写到文件中的流对象。

需求:将字符串”你好”按照utf-8编码方式输出到D:\out.txt文件中。
实现代码如下所示:
分析和步骤:
1)创建一个可以给文件中写数据的字节输出流FileOutputStream类的对象fos,D:\out.txt作为参数;
2)创建字符转成字节的输出转换流OutputStreamWriter类的对象osw,并指定编码方式是utf-8;
3)使用osw对象调用write()函数将字符串对象”你好”写到指定的文件位置;
4)关闭转换流资源;

/*
 * 字符转字节输出转换流
 */
public class OutputStreamWriterDemo {

    public static void main(String[] args) throws IOException {
        //创建一个可以给文件中写数据的字节输出流对象
        FileOutputStream fos = new FileOutputStream("D:\\out.txt");
        //创建字符数据转字节数据输出转换流对象
        OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");
        osw.write("你好");
        osw.close();
    }
}

注意:这里也有个缓冲区,也就是说写数据的时候先将数据写到缓冲区中,然后必须要调用flush()或者close()函数才能将缓冲区中的数据写到文件中。

1.4、转换流的细节

1、转换流的构造函数中接收的字节流都是负责和底层文件交互的流。
2、如果使用转换流没有传递编码表,就和FileReader或FileWriter没有任何区别。
3、只有输入的字节数据转换字符数据的转换流,没有输入的字符数据转字节数据的转换流。
4、只有输出的字符数据转字节数据的转换流,没有字节数据转字符数据的输出转换流。


7.png

2、编码、解码、乱码(掌握)

在转换流中,字符数据转字节数据,把字符数据编码成字节。字节转换成字符,把字节解码成字符。

编码:把字符数据通过查阅编码表,查到对应的编码值,然后把编码值转成字节数据的过程。
字符------> 字节的过程(编码)
字符可以使用字符串表示,字节可以使用字节数组表示。
编码:把看得懂的数据变成看不懂的数据(字符 ---> 字节)。

需求:把字符串”你好”进行编码。
分析:将一个字符串变为一个字节数据需要借助String类中的getBytes()和getBytes(String charsetName)函数,不带参数的函数是按照默认的字符集将字符串编码为字节数据,而带有参数的函数是将字符串根据指定的编码编码为字节数据。

byte[] getBytes() 使用平台的默认字符集将此 String 编码为 byte 序列
byte[] getBytes(String charsetName) 使用指定的字符集将此 String 编码为 byte 序列

代码如下所示:
分析和步骤:
1)定义一个EncodeDemo 测试类;
2)在这个类中定义一个字符串str,并赋值为”你好”;
3)使用字符串str对象调用getBytes()函数根据系统默认编码表将字符串”你好”转换成字节数组;
4)使用Arrays数组工具类调用toString()函数打印生成的字节数组;
5)在使用字符串str对象调用getBytes(String charsetName)函数根据指定的编码表将字符串”你好”转换成字节数组;

/*
 * 编码演示
 * 编码就是把看得懂的数据变为看不懂的数据
 * 字符-----》字节
 * 需求:把字符串”你好”进行编码。
 */
public class EncodeDemo {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String str="你好";
        //使用getBytes()使用平台的默认字符集将此 String 编码为 byte 序列,
        //并将结果存储到一个新的 byte 数组中。
        byte[] bytes = str.getBytes();//默认编码表 
        System.out.println(Arrays.toString(bytes));//[-60, -29, -70, -61]
        
        //getBytes(Charset charset) 
        //使用给定的 charset 将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。
        byte[] bytes2 = str.getBytes("GBK");//GBK编码
        System.out.println(Arrays.toString(bytes2));//[-60, -29, -70, -61]
        
        byte[] bytes3 = str.getBytes("UTF-8");//UTF-8编码
        System.out.println(Arrays.toString(bytes3));//[-28, -67, -96, -27, -91, -67]
    }
}

说明:
1)GBK编码表:一个中文占两个字节;
2)在这里UTF-8编码表:一个中文占三个字节。

解码:把看不懂的变成看得懂的(字节 --> 字符)
需求:把上述字符串”你好”生成的字节数据解码成为字符串。

分析:
解码:
String(byte[] bytes) 通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
String(byte[] bytes, String charsetName) 通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String。

代码如下所示:
分析和步骤:
1)定义一个DecodeDemo 测试类;
2)在这个类中定义一个byte类型的字节数组b,将上述字符串”你好”按照gbk和utf-8生成的编码分别作为数组中的数据;
3)使用String类中的构造函数将字节作为数组,按照系统默认编码方式和指定的编码方式utf-8进行解码生成一个新的字符串;
4)输出生成新的字符串;

/*
 * 解码演示
 * 需求:把上述字符串”你好”生成的字节数据解码成为字符串。
   解码:把看不懂的转成看得懂的(字节-----》字符)
 */
public class DecodeDemo {
    public static void main(String[] args) throws UnsupportedEncodingException {
        //定义一个字节数组 你好的gbk字节形式
//      byte[] b={-60, -29, -70, -61};
        //你好的UTF-8字节形式
        byte[] b2={-28, -67, -96, -27, -91, -67};
        /*
         * String(byte[] bytes) 
         * 通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
         */
//      String s = new String(b);//默认编码表解码
//      System.out.println(s);//你好
        /*
         * String(byte[] bytes, String charsetName) 
         * 通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String。
         */
//       String s2 = new String(b,"GBK");//你好
        //解码
         String s2 = new String(b2,"UTF-8");
         System.out.println(s2);//你好
    }
}

乱码:编码和解码的时候使用的编码表不一致。导致解码出来的数据是错误的。

代码如下所示:
分析和步骤:
1)定义一个LuanCodeDemo 测试类;
2)在这个类中定义一个字符串str,并赋值为”你好”;
3)使用字符串str对象调用getBytes(“UTF-8”)函数根据指定的UTF-8编码表将字符串”你好”转换成字节数组bytes;
4)解码 ,使用String类的构造函数以GBK的编码表进行解码,但是这样会发生乱码;
5)由于编码使用UTF-8方式进行编码,所以解码也得使用UTF-8,否则会乱码;
6)既然上述使用GBK解码方式已经发生乱码了,我们应该在此基础上进行解决乱码的问题,我们需要使用生成的字符串对象调用getBytes()函数将字符串以gbk编码表在转换为字节数组进行编码;
7)重新使用正确的编码表UTF-8进行解码;

/*
 * 乱码演示
 */
public class LuanCodeDemo {
    public static void main(String[] args) throws UnsupportedEncodingException {
        //定义字符串对象
        String str="你好";
        //编码
        byte[] bytes = str.getBytes("UTF-8");
        //解码 由于编码采用的是UTF-8,而解码采用的是GBK这样会发生乱码
        String str2 = new String(bytes,"GBK");//浣犲ソ
        //解码 由于编码采用的是UTF-8,而解码采用的也是UTF-8这样不会发生乱码
//      String str3 = new String(bytes,"UTF-8");//你好
        
        //输出解码后的结果 
        System.out.println(str2);//浣犲ソ
//      System.out.println(str3);//你好
        /*
         * 上述乱码的原因是将字符串str="你好"以UTF-8进行编码
         * 然后以GBK进行解码
         * 编码和解码的码表不一致 所以会发生乱码
         * 那么这里我们只能再次按照GBK编码表再次编码
         */
        //编码
        byte[] bytes2 = str2.getBytes("GBK"); 
        //[-28, -67, -96, -27, -91, -67]
        System.out.println(Arrays.toString(bytes2));
        //重新用正确的编码进行解码
        String str4 = new String(bytes2,"UTF-8");
        System.out.println(str4);//你好
    }
}

上述代码的图解如下图所示:


8.png

说明:
发生乱码:按照反方向编码回去,编码的时候一定要使用导致解码错误的编码表,得到编码后的数据,再使用正确的编码表进行解码。这种方式不是万能的。一旦解码中有未知的错误字符,就无法还原。
比如某些字符串中的汉字 ”联通” 就会发生未知字符的现象。 结果是:联? 这里的?就表示未知字符。

总结:
上述对于联通解码不出来,原因是由于在GBK码表中具有未知字符,而对于未知字符,解码的时候也会按照未知字符进行处理,所以为了发生上述情况,我们以后学习的软件(比如:服务器Tomcat)中底层的编码表全部是没有未知字符的编码表ISO-8859-1。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 编码问题一直困扰着开发人员,尤其在 Java 中更加明显,因为 Java 是跨平台语言,不同平台之间编码之间的切换...
    x360阅读 2,465评论 1 20
  • 字符是用户可以读写的最小单位。计算机所能支持的字符组成的集合,就叫做字符集。字符集通常以二维表的形式存在。二维表的...
    刘惜有阅读 8,070评论 2 14
  • 编码与解码 码表: 常见的码表如下:ASCII : 美国标准信息交换码。用一个字节的7位可以表示。ISO8859-...
    奋斗的老王阅读 1,182评论 0 51
  • (20180626) 我选择沉默的主要原因之一:从话语中,你很少能学到人性,从沉默中却能。假如还想学得更多,那就要...
    永远的浩子阅读 473评论 0 0
  • 人 ,走着,走着,会无知的疲倦; 心,飞着,飞着,会莫名的寂寥。 只愿许我一片青天,我便能自由翱翔于九天之上; 只...
    心灵简约阅读 383评论 0 0