第11天 IO流
今日内容介绍
    转换流
    序列化流
    打印流
    Properties
今日学习目标
    能够阐述编码表的意义
    能够使用转换流读取指定编码的文本文件
    能够使用转换流写入指定编码的文本文件
    能够使用Properties的load方法加载文件中配置信息
    能够使用Properties的store方法保存配置信息到文件
    能够说出打印流的特点
    能够使用序列化流写出对象到文件
    能够使用反序列化流读取文件到程序中
    能够导入commons-io工具包
    能够配置classpath添加jar包到项目中
    能够使用FileUtils常用方法操作文件

第1章    字符流
经过前面的学习,我们基本掌握的文件的读写操作,在操作过程中字节流可以操作所有数据,可是当我们操作的文件中有中文字符,并且需要对中文字符做出处理时怎么办呢?
1.1    字节流读取字符的问题
通过以下程序读取带有中文件的文件。

public class CharStreamDemo {
    public static void main(String[] args) throws IOException {
        //给文件中写中文
        writeCNText();
        //读取文件中的中文
        readCNText();
    }   
    //读取中文
    public static void readCNText() throws IOException {
        FileInputStream fis = new FileInputStream("c:\\cn.txt");
        int ch = 0;
        while((ch = fis.read())!=-1){
            System.out.println(ch);
        }

    }
    //写中文
    public static void writeCNText() throws IOException {
        FileOutputStream fos = new FileOutputStream("c:\\cn.txt");
        fos.write("a欢迎你".getBytes());
        fos.close();
    }
}

上面程序在读取含有中文的文件时,我们并没有看到具体的中文,而是看到一些数字,这是什么原因呢?既然看不到中文,那么我们如何对其中的中文做处理呢?要解决这个问题,我们必须研究下字符的编码过程。

1.2    字符编码表
我们知道计算机底层数据存储的都是二进制数据(二进制了解吗?最高位表示什么?),而我们生活中的各种各样的数据,如何才能和计算机中存储的二进制数据对应起来呢?
这时老美他们就把每一个字符和一个整数对应起来,就形成了一张编码表,老美他们的编码表就是ASCII表。其中就是各种英文字符对应的编码。
编码表:其实就是生活中字符和计算机二进制的对应关系表。
1、ascii: 一个字节中的7位就可以表示。对应的码值都是正数。00000000-0xxxxxxx(其他码表都要兼容它)  0000 0000
2、iso-8859-1:拉丁码表 latin,用了一个字节用的8位。1-xxxxxxx  负数。
3、GB2312:简体中文码表。包含6000-7000中文和符号。用两个字节表示。中文的两个字节都是开头为1 ,即两个字节都是负数。
         GBK:目前最常用的中文码表,2万的中文和符号。用两个字节表示,其中的一部分文字,第一个字节开头是1,第二字节开头是0
          GB18030:最新的中文码表,目前还没有正式使用。やめて
4、unicode:国际标准码表:无论是什么文字,都用两个字节存储。
    Java中的char类型用的就是这个码表。char c = 'a';占两个字节。
    Java中的字符串是按照系统默认码表来解析的。简体中文版 字符串默认的码表是GBK。
5、UTF-8:基于unicode,一个字节就可以存储的数据,不用两个字节存储,而且这个码表更加的标准化,在每一个字节头加入了编码信息(后期到api中查找)。
能识别中文的码表:GBK、UTF-8;正因为识别中文码表不唯一,涉及到了编码解码问题。
对于我们开发而言;常见的编码 GBK  UTF-8  ISO-8859-1
1.GBK:中文2字节
2.UTF-8:中文3字节

文字--->(数字) :编码: 就是看能看懂内容,转换成看不懂的内容。a 0110 0001
(数字)--->文字  : 解码: 就是把看不懂的内容,转换成看懂的内容。0110 0001   --a

1.3    方便程序员的IO流
在IO开发过程中,我们传输最频繁的数据为字符,

我爱你---> 10100101010100110010101001--->妹子

而以字节方式传输字符需要每次将字符串转换成字节再处理,而且也丧失了程序员对数据内容的判断(因为程序员只认识字符,不认识字节)。所以,为了让程序员方便对字符进行操作,Java提供了专门以字符作为操作单位的类——字符流,其底层仍然为字节流。
显然,字符流只能操作字符,无法操作其他数据,如声音、视频等。
在基础班我们已经学习过字符流了,这里就进行一个简单的回顾
1.4    字符流回顾
    IO流的分类
|- 字节流
   |- 字节输入流 InputStream 抽象类
       |-  FileInputStream 操作文件的字节输入流
|- 字节输出流 OuputStream抽象类
   |- FileOutputStream 操作文件的字节输出流
|- 字符流
   |- 字符输入流 Reader抽象类
           |- FileReader 用来操作文件的字符输入流(简便的流)
   |- 字符输出流 Writer抽象类
           |- FileWriter 用来操作文件的字符输出流(简便的流)
Java中流的命名 规范  功能+类型

第2章    转换流

在学习字符流(FileReader、FileWriter)的时候,其中说如果需要指定编码和高效区大小时,可以在字节流的基础上,构造一个InputStreamReader或者OutputStreamWriter,这又是什么意思呢?
2.1    字符编码表(重复内容)
我们知道计算机底层数据存储的都是二进制数据,而我们生活中的各种各样的数据,如何才能和计算机中存储的二进制数据对应起来呢?
这时老美他们就把每一个字符和一个整数对应起来,就形成了一张编码表,老美他们的编码表就是ASCII表。其中就是各种英文字符对应的编码。
编码表:其实就是生活中字符和计算机二进制的对应关系表。
1、ascii: 一个字节中的7位就可以表示。对应的字节都是正数。0-xxxxxxx
2、iso-8859-1:拉丁码表 latin,用了一个字节用的8位。1-xxxxxxx  负数。
3、GB2312:简体中文码表。包含6000-7000中文和符号。用两个字节表示。两个字节都是开头为1 ,两个字节都是负数。
         GBK:目前最常用的中文码表,2万的中文和符号。用两个字节表示,其中的一部分文字,第一个字节开头是1,第二字节开头是0
          GB18030:最新的中文码表,目前还没有正式使用。
4、unicode:国际标准码表:无论是什么文字,都用两个字节存储。
    Java中的char类型用的就是这个码表。char c = 'a';占两个字节。
    Java中的字符串是按照系统默认码表来解析的。简体中文版 字符串默认的码表是GBK。
5、UTF-8:基于unicode,一个字节就可以存储数据,不要用两个字节存储,而且这个码表更加的标准化,在每一个字节头加入了编码信息(后期到api中查找)。
能识别中文的码表:GBK、UTF-8;正因为识别中文码表不唯一,涉及到了编码解码问题。
对于我们开发而言;常见的编码 GBK  UTF-8  ISO-8859-1
文字--->(数字) :编码: 就是看能看懂内容,转换成看不懂的内容。
(数字)--->文字  : 解码: 就是把看不懂的内容,转换成看懂的内容。

2.2    OutputStreamWriter类(转换流,快递运输的思想)
查阅OutputStreamWriter的API介绍,OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的字符编码表,将要写入流中的字符编码成字节。它的作用的就是,将字符串按照指定的编码表转成字节,在使用字节流将这些字节写出去。

转换流,打印流,序列化-LMLPHP


    代码演示:

public static void writeCN() throws Exception {
        //创建与文件关联的字节输出流对象
        FileOutputStream fos = new FileOutputStream("c:\\cn8.txt");
        //创建可以把字符转成字节的转换流对象,并指定编码
        OutputStreamWriter osw = new OutputStreamWriter(fos,"utf-8");
        //调用转换流,把文字写出去,其实是写到转换流的高效区中
        osw.write("你好");//写入高效区。
        osw.close();
    }

OutputStreamWriter流对象,它到底如何把字符转成字节输出的呢?
其实在OutputStreamWriter流中维护自己的高效区,当我们调用OutputStreamWriter对象的write方法时,会拿着字符到指定的码表中进行查询,把查到的字符编码值转成字节数存放到OutputStreamWriter高效区中。然后再调用刷新功能,或者关闭流,或者高效区存满后会把高效区中的字节数据使用字节流写到指定的文件中。

2.3    InputStreamReader类
查阅InputStreamReader的API介绍,InputStreamReader 是字节流通向字符流的桥梁:它使用指定的字符编码表读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

转换流,打印流,序列化-LMLPHP


    代码演示

public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
        //演示字节转字符流的转换流
        readCN();
    }
    public static void readCN() throws IOException{
        //创建读取文件的字节流对象
        InputStream in = new FileInputStream("c:\\cn8.txt");
        //创建转换流对象 
        //InputStreamReader isr = new InputStreamReader(in);这样创建对象,会用本地默认码表读取,将会发生错误解码的错误
        InputStreamReader isr = new InputStreamReader(in,"utf-8");
        //使用转换流去读字节流中的字节
        int ch = 0;
        while((ch = isr.read())!=-1){
            System.out.println((char)ch);
        }
        //关闭流
        isr.close();
    }
}

注意:在读取指定的编码的文件时,一定要指定编码格式,否则就会发生解码错误,而发生乱码现象。

2.4    转换流和子类区别
发现有如下继承关系:
Writer 字符输出流
|- OutputStreamWriter   转换流(字符流—>字节流)(属于字符输出流, 可以指定字符编码表,用来写入数据到文件)
           |--FileWriter 操作文件中字符输出流,采用默认的字符编码表
fw.write(“你好”)
Reader 字符输入流
|- InputStreamReader: 转换流(字节流字符流)(属于字符输入流,可以指定字符编码表,用来从文件中读数据)
|--FileReader操作文件中字符输入流,采用默认的字符编码表

父类和子类的功能有什么区别呢?
OutputStreamWriter和InputStreamReader是字符和字节的桥梁:也可以称之为字符转换流。字符转换流原理:字节流+编码表。
FileWriter和FileReader:作为子类,仅作为操作字符文件的便捷类存在。当操作的字符文件,使用的是默认编码表时可以不用父类,而直接用子类就完成操作了,简化了代码。

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));//默认GBK字符集。
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"GBK");//指定GBK字符集。
FileReader fr = new FileReader("a.txt");

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“a.txt”));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“a.txt”),”GBK”);

FileWriter fw = new FileWriter(“a.txt”)

这三句代码的功能是一样的,其中第三句最为便捷。
注意:一旦要指定其他编码时,绝对不能用子类,必须使用字符转换流。什么时候用子类呢?
条件:
1、操作的是文件。2、使用默认编码。
总结:
字节--->解码--->字符 : 看不懂的--->看的懂的。  需要读。输入流。 InputStreamReader
字符--->编码--->字节 : 看的懂的--->看不懂的。  需要写。输出流。 OutputStreamWriter

第3章    序列化流与反序列化流
用于从流中读取对象的操作流 ObjectInputStream   称为 反序列化流
用于向流中写入对象的操作流 ObjectOutputStream   称为 序列化流
    特点:用于操作对象。可以将对象写入到文件中,也可以从文件中读取对象。

3.1    对象序列化流ObjectOutputStream
ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。
注意:只能将支持 java.io.Serializable 接口的对象写入流中

转换流,打印流,序列化-LMLPHP


    代码演示:

public class ObjectStreamDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        /*
         * 将一个对象存储到持久化(硬盘)的设备上。
         */
        writeObj();//对象的序列化。
    }
    public static void writeObj() throws IOException {
        //1,明确存储对象的文件。
        FileOutputStream fos = new FileOutputStream("tempfile\\obj.object");
        //2,给操作文件对象加入写入对象功能。
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        //3,调用了写入对象的方法。
        oos.writeObject(new Person("wangcai",20));
        //关闭资源。
        oos.close();
    }
}

    Person类

public class Person implements Serializable {
    private String name;
    private int age;
    public Person() {
        super();
    }
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}

3.2    对象反序列化流ObjectInputStream
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。支持 java.io.Serializable接口的对象才能从流读取。

转换流,打印流,序列化-LMLPHP


    代码演示

public class ObjectStreamDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        readObj();//对象的反序列化。
    }
    public static void readObj() throws IOException, ClassNotFoundException {

        //1,定义流对象关联存储了对象文件。
        FileInputStream fis = new FileInputStream("tempfile\\obj.object");

        //2,建立用于读取对象的功能对象。
        ObjectInputStream ois = new ObjectInputStream(fis);

        Person obj = (Person)ois.readObject();

        System.out.println(obj.toString());

    }
}

3.3    序列化接口(主要是介绍两个异常)
当一个对象要能被序列化,这个对象所属的类必须实现Serializable接口。否则会发生异常NotSerializableException异常。
同时当反序列化对象时,如果对象所属的class文件在序列化之后进行的修改,那么进行反序列化也会发生异常InvalidClassException。发生这个异常的原因如下:
    该类的序列版本号与从流中读取的类描述符的版本号不匹配
    该类包含未知数据类型
    该类没有可访问的无参数构造方法
Serializable标记接口。该接口给需要序列化的类,提供了一个序列版本号。serialVersionUID. 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

    代码修改如下,修改后再次写入对象,读取对象测试

public class Person implements Serializable {
    //给类显示声明一个序列版本号。
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    public Person() {
        super();

    }
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}

3.4    瞬态关键字transient
当一个类的对象需要被序列化时,某些属性不需要被序列化,这时不需要序列化的属性可以使用关键字transient修饰。只要被transient修饰了,序列化时这个属性就不会被序列化了。
同时静态修饰也不会被序列化,因为序列化是把对象数据进行持久化存储,而静态的属于类加载时的数据,不会被序列化。
    代码修改如下,修改后再次写入对象,读取对象测试

public class Person implements Serializable {
    /*
     * 给类显示声明一个序列版本号。
     */
    private static final long serialVersionUID = 1L;
    private static String name;
    private transient/*瞬态*/ int age;

    public Person() {
        super();

    }

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}

第4章    打印流
4.1    打印流的概述
打印流添加输出数据的功能,使它们能够方便地打印各种数据值表示形式.
打印流根据流的分类:
    字节打印流   PrintStream
    字符打印流   PrintWriter
    方法:
void print(String str): 输出任意类型的数据,
void println(String str): 输出任意类型的数据,自动写入换行操作
    代码演示:

 /* 
 * 需求:把指定的数据,写入到printFile.txt文件中
 * 
 * 分析:
 *     1,创建流
 *     2,写数据
 *     3,关闭流
 */
public class PrintWriterDemo {
    public static void main(String[] args) throws IOException {
        //创建流
        //PrintWriter out = new PrintWriter(new FileWriter("printFile.txt"));
        PrintWriter out = new PrintWriter("printFile.txt");
        //2,写数据
        for (int i=0; i<5; i++) {
            out.println("helloWorld");
        }
        //3,关闭流
        out.close();
    }
}

第5章    commons-IO

5.1    导入classpath
加入classpath的第三方jar包内的class文件才能在项目中使用
使用第三方类库的步骤:
1.创建lib文件夹
2.将commons-io.jar拷贝到lib文件夹
3.右键点击commons-io.jar,Build Path→Add to Build Path

5.2    FileUtils

提供文件操作(移动文件,读取文件,检查文件是否存在等等)的方法。
    常用方法:

readFileToString(File file):读取文件内容,并返回一个String;
writeStringToFile(File file,String content):将内容content写入到file中;
copyFile(File srcFile, File destFile): 文件复制
copyDirectoryToDirectory(File srcDir,File destDir);文件夹复制

    代码演示:

/*
 * 普通方式,完成文件的复制
 */
public class CommonsIODemo01 {
    public static void main(String[] args) throws IOException {
        //method1("D:\\test.avi", "D:\\copy.avi");

        //通过Commons-IO完成了文件复制的功能
        FileUtils.copyFile(new File("D:\\test.avi"), new File("D:\\copy.avi"));
    }

    //文件的复制
    private static void method1(String src, String dest) throws IOException {
        //1,指定数据源 
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(src));
        //2,指定目的地
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dest));
        //3,读
        byte[] buffer = new byte[1024];
        int len = -1;
        while ( (len = in.read(buffer)) != -1) {
            //4,写
            out.write(buffer, 0, len);
        }
        //5,关闭流
        in.close();
        out.close();
    }
}

/*
 * 使用commons-io完成文件、文件夹的复制
 */
public class CommonsIODemo02 {
    public static void main(String[] args) throws IOException {
        //通过Commons-IO完成了文件复制的功能
        FileUtils.copyFile(new File("D:\\test.avi"), new File("D:\\copy.avi"));

        //通过Commons-IO完成了文件夹复制的功能
        //D:\基础班 复制到 C:\\abc文件夹下
        FileUtils.copyDirectoryToDirectory(new File("D:\\基础班"), new File("C:\\abc"));
    }
}

     ​欢迎关注菜鸟永恒,点滴记录,共同进步。

01-15 04:00