Skip to content

第五章 字符流

5.1 字符编码

文件叫 d.txt

1641718001973

1641718015075

**请问,计算机底层存储的时候 是存的 a 黑马 吗? **

不是,是 a 黑马的字节形式,7 个字节?

我们如果使用字节流去读取中文文件,按照字节形式读取到的内容如下,

1641718360297

很多同学百思不得其解,为啥呀!!

对着呢,我用字节流读取数据,读的就是字节呀!!!!!

计算机是不认识自然界中的字符的,它只认识 01 代码也就是所谓二进制(字节),怎么办呢? 美国人那边就想了一种方法,让自然界中的字符与二进制产生对应关系,就形成了编码表。

最初的编码表只有英文字符。

编码: 通过编码表,把我们看得懂的 字符--->字节 看不懂的。 解码:通过编码表,把我们看不懂的字节 ---->字符 我们看得懂的。

java
package com.itheima.charactor;
import java.io.FileInputStream;
import java.io.IOException;
public class Demo01 {
public static void main(String[] args) throws IOException {
    //读取 d.txt文件
    FileInputStream fis = new FileInputStream("day11\\d.txt");
    //使用字节读取
    int b;
    while((b=fis.read())!=-1){
   	 	System.out.println((byte)b);
    }
    	fis.close();
    }
}

5.2 常见编码介绍

计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照 A 规则存储,同样按照 A 规则解析,那么就能显示正确的文本 f 符号。反之,按照 A 规则存储,再按照 B 规则解析,就会导致乱码现象。

编码: 字符转换为字节。

解码:字节转换为字符。

  • 字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。

    生活中的文字和计算机文字的对应关系 a-->97-->0110 0001

  • 字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。

计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有 ASCII 字符集、GBK 字符集、Unicode 字符集等。

可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。

  • ASCII 字符集
    • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
    • 基本的 ASCII 字符集,使用 7 位(bits)表示一个字符,共 128 字符。ASCII 的扩展字符集使用 8 位(bits)表示一个字符,共 256 字符,方便支持欧洲常用字符。
  • ISO-8859-1 字符集
    • 拉丁码表,别名 Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
    • ISO-8859-1 使用单字节编码,兼容 ASCII 编码。
  • GBxxx 字符集
    • GB 就是国标的意思,是为了显示中文而设计的一套字符集。
    • GB2312:简体中文码表。一个小于 127 的字符的意义与原来相同。但两个大于 127 的字符连在一起时,就表示一个汉字,这样大约可以组合了包含 7000 多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在 127 号以下的那些就叫"半角"字符了。
    • GBK:最常用的中文码表。是在 GB2312 标准基础上的扩展规范,使用了双字节编码方案,共收录了 21003 个汉字,完全兼容 GB2312 标准,同时支持繁体汉字以及日韩汉字等。
      • 中文版操作系统使用的编码表就是 GBK。
      • 中文汉字在 2312 和 GBK 编码表中均为两个字节表示,第一个字节为负数,第二个字节可能是负数也可能是正数。
    • GB18030:最新的中文码表。收录汉字 70244 个,采用多字节编码,每个字可以由 1 个、2 个或 4 个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。尚未正式启用。
  • Unicode 字符集
    • Unicode 编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
    • 它最多使用 4 个字节的数字来表达每个字母、符号,或者文字。有三种编码实现方案,UTF-8、UTF-16 和 UTF-32。最为常用的 UTF-8 编码。
    • UTF-8 编码,可以用来表示 Unicode 标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持 UTF-8 编码。所以,我们开发 Web 应用,也要使用 UTF-8 编码。它使用一至四个字节为每个字符编码,编码规则:
      1. 128 个 US-ASCII 字符,只需一个字节编码。
      2. 拉丁文等字符,需要二个字节编码。
      3. 大部分常用字(含中文),使用三个字节编码。
      4. 其他极少使用的 Unicode 辅助字符,使用四字节编码。
      5. UTF-8 是变长编码表,汉字在 UTF-8 中均为负数

5.3 字节流读取数据和字符流读取数据对比

字节流读取数据

java
package com.itheima.fileReader;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Arrays;

public class FileReaderDemo {

    public static void main(String[] args) throws Exception {
        /*
        使用字节流 读取 a.txt
           使用字节数组去读取
         */
        //1:创建输入流
        FileInputStream fis = new FileInputStream("day12\\a.txt");
        //2: 执行读操作
        byte[] buffer = new byte[10];
        int len = fis.read(buffer);//数据读到了 buffer中  有效字节数
        System.out.println(len);
        //现在有效的字节数据   在哪?
        System.out.println(Arrays.toString(buffer));//观察 有效字节数 是前六个
        // 看不懂的数据 ---看得懂  需要做  解码
        String s = new String(buffer, 0, len);
        System.out.println("我们手动解码之后 字节-字符:"+s);
        //3: 释放资源
        fis.close();
    }
}

字符流读取数据

java
package com.itheima.fileReader;

import java.io.FileInputStream;
import java.io.FileReader;
import java.util.Arrays;
/*
    FileReader  字符输入流  可以实现 直接读取字符操作
      不需要我们做解码

      大胆的说
        字符流 =  字节流 + 编码表
 */
public class FileReaderDemo2 {

    public static void main(String[] args) throws Exception {
       /*
        使用字符流 读取 day12\\a.txt
        */
       //1:创建输入流对象
        FileReader fr = new FileReader("day12\\a.txt");
        //2:读取
        //定义字符数组
        char[] buffer = new char[3];
        //读
        int len = fr.read(buffer);//使用字符数组接收 读取来的字符 读的有效字符个数 len个
        System.out.println("有效字符"+len);
        System.out.println("查看数组:"+Arrays.toString(buffer));
        //3:释放资源
        fr.close();
    }
}

1641778987486

5.4 字符流的使用

字符流的出现,是让程序员与字节的交流中解脱出来,也就是 再读文件,这个解码的操作, 就教给流去完成,我们不用再关注与解码动作了。

你只关注读出来的字符是什么就行了。

简而言之 就是我们可以一个字符一个字符的读取。

我们没有做解码 谁做了呢?

字符流 = 字节流 + 编码表。

字符流的顶级父类

字符输入流 顶级父类 Reader--抽象的

​ 常用的类 FileReader

常用方法:

java
  int read() 一次读一个字符 使用int类型接收,返回值就是读取到字符int形式,如果结果是-1读到末尾了。
  int read(char[] buffer)一次读取一个字符数组,使用int接收读取到有效字符个数。
                         如果个数是-1读到末尾了。
  close()

字符输出流 顶级父类 Writer --抽象的

​ 常用的类 FileWriter

常用方法

java
    这里有五个写方法
     *       write(char c) 写一个字符
     *       write(char[] chs)写一个字符数组
     *       write(char[] chs,int off,int len)写字符数组一部分
     *       write(String s)写一个字符串
     *       write(String s,int off,int len)写一个字符串一部分

**字符输出流 **

​ FileWriter

FileWriter(String path)
FileWriter(File file)
FileWriter(String path,boolean append)
FileWriter(File file,boolean append)
java
package com.itheima.fileWriter;

import java.io.FileWriter;

public class FileWriterDemo01 {

    public static void main(String[] args) throws Exception{
        //使用字符输出流  写一个数据 詹姆斯  到 day12\\nba.txt 文件中
        //1:创建字符输出流
        FileWriter fw = new FileWriter("day12\\nba.txt");
        //2: 写
        fw.write("詹姆斯");
        fw.write("\r\n");
        fw.write("杜兰特");
        //3: 释放资源
        fw.close();

    }
}

如果我不释放资源 文件中有没有数据

1628241575584

java
package com.itheima.fileWriter;

import java.io.FileWriter;

public class FileWriterDemo02 {

    public static void main(String[] args) throws Exception{
        //使用字符输出流  写一个数据 詹姆斯  到 day12\\nba.txt 文件中
        //1:创建字符输出流
        FileWriter fw = new FileWriter("day12\\nba.txt");
        //2: 写
        fw.write("詹姆斯");
        //刷新
       // fw.flush();

        fw.write("杜兰特");
//        fw.close();  close不能使用在 刷新之前 因为close包含了刷新同时也释放流资源
        fw.flush();
        //3: 释放资源
        fw.close();

    }
}

注意:字符串输出数据,必须要使用 flush()方法,否则数据在内存中,不会到达指定文件,close()方法在关闭前也会进行刷新,推荐写一次刷新一次,避免内存占用过多。

1641782362960

字符输入流

FileReader

​ 一次读一个字符

​ 一次读一个字符数组

1628238067219

java
package com.itheima.reader;

import java.io.FileReader;
import java.io.IOException;

public class ReaderDemo {

    public static void main(String[] args) throws IOException {
        //创建字符输入流 用于读取 文本文件 nba.txt
        FileReader fr = new FileReader("day11\\nba.txt");

        //读数据  采用字符数组形式
        char[] chs = new char[24];
        int len = fr.read(chs);
//        System.out.println("读了几个字符:"+len);

        for (int i = 0; i < len; i++) {
            System.out.print(chs[i]);
        }


        fr.close();


    }
}

5.4 字符高效流的使用

1628241616360

1628241624048

java
package com.itheima.buffer;

import java.io.*;

public class BufferDemo {

    public static void main(String[] args) throws Exception {
        //字符缓冲流
        //写的操作
//         write();
           //读取四行数据
        FileReader fr = new FileReader("day12\\cba.txt");
        //你不能 你的装饰流  BufferReader
        BufferedReader br = new BufferedReader(fr);

//        String s1 = br.readLine();//一次读一行 读完指向下一行
//        String s2 = br.readLine();//一次读一行 读完指向下一行
//        String s3 = br.readLine();//一次读一行 读完指向下一行
//        String s4 = br.readLine();//一次读一行 读完指向下一行
//        String s5 = br.readLine();//一次读一行 读完指向下一行
//
//        System.out.println(s1);
//        System.out.println(s2);
//        System.out.println(s3);
//        System.out.println(s4);
//        System.out.println(s5);
        //适用循环优化
        String s ;//s 用于接收读取到的每一行数据
        while((s=br.readLine())!=null){//不等于null说明这一行 读到数据
            System.out.println(s);
        }
        //释放资源
        br.close();
        fr.close();

    }


    public static void write() throws Exception {
        //字符缓冲流
        //写的操作
        //使用字符缓冲输出流关联  基本的字符输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("day12\\cba.txt"));
        //之前的方法都适用
        bw.write("林书豪");
        bw.newLine();//是可以匹配 不同的操作系统 增强适用性
        bw.write("郭艾伦");
        bw.newLine();
        bw.write("原帅");
        bw.newLine();
        bw.write("艾弗森");
        //释放资源
        bw.close();

    }
}

5.5 文本练习

请将文本信息恢复顺序。

properties
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。

案例分析

  1. 使用流对象逐行读取原文本信息,把读取的信息保存到集合中。
  2. 使用 Collections 集合工具类中的方法 sort,对集合中的元素按照自定义规则排序。
  3. 遍历集合,把集合中排序后的文本在写入到新的记事本中。
java
package com.itheima.test;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;

public class Test01 {
    /*
       把 厨师表 中的  每一行数据  进行排序  --输出一个正确顺序的  出师表.txt

       1:把出师表的每一行数据读取来。
          遍历的时候使用一个集合 进行 收集每一行数据。
       2:使用 Collections.sort(List<?> list)
            一个参数代表 不改规则  排序比较的规则
            两个参数的话 第二个参数是不是可以更改排序的规则
       3:排完序了  集合中的内容是不是有顺序了
       4:遍历 一行行的 使用字符输出流 输出到一个  出师表.txt文件中
     */
    public static void main(String[] args) throws Exception {
        //1: 创建字符输入流 --- 创建高效的字符流
        FileReader fr = new FileReader("day12\\厨师表.txt");
        BufferedReader br = new BufferedReader(fr);
        //2: 读取的时候  进行收集
        // 创建一个收集每一行的 集合
        ArrayList<String>  list = new ArrayList<>();
        //3 读一行 add一行
        String line ;
        while((line=br.readLine())!=null){//读到每一行 line
            //把line添加到list
            list.add(line);
        }
        // 4 对集合list进行排序
        Collections.sort(list);

       // 5  排好顺序在  list集合  变遍历变 写
        //  需要 一个字符输出流  ---换行 使用 字符缓冲流
        FileWriter fw = new FileWriter("day12\\出师表.txt");
        BufferedWriter bw = new BufferedWriter(fw);

        //遍历
        for (String s : list) {
            bw.write(s);
            bw.newLine();
        }


        //释放资源
        bw.close();
        fw.close();
        br.close();
        fr.close();
    }
}

补充章节 IO 流中的异常处理

java
/*
    IO流代码中的异常处理
        1.throws: 声明抛出异常
        2.try-catch: 捕获处理异常
            try{
                有可能产生异常的代码
            } catch(异常类 对象名){
                异常处理的代码
            } finally{
                释放资源的代码
            }
    还可以使用JDK7优化后的 try-with-resource 语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。
    	try(流对象的定义){
    		有可能产生异常的代码
    	}catch(异常类 对象名) {
    		异常处理的代码
    	}
 */
public class Demo05IOException {
    public static void main(String[] args) {
        //提升变量的作用域,进行初始化
        //不初始化的话,在创建对象时,如果出异常,变量没有值,不能调用方法
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("day11_xw\\io\\from\\jzc.flv");
            fos = new FileOutputStream("day11_xw\\io\\to\\jzc.flv");
            byte[] bs = new byte[1024*8];
            int len = 0;
            while ((len = fis.read(bs)) != -1) {
                fos.write(bs,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //必须分开处理
            //如果try中new对象出了异常,变量的初始值都是null
            //调用方法,报出空指针异常
            if(fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Released under the MIT License.