JavaSE-IO操作


第一章:IO介绍

1.1 什么是IO

​ 生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了 ctrl+s ,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘、内存、硬盘、外接设备等等。

​ 我们把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为 输入input 和 输出output ,即流向内存是输入流,流出内存的输出流

​ Java中I/O操作主要是指使用 java.io 包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。

1.2 IO的分类

根据数据的流向分为:输入流和输出流。

  • 输入流 :把数据从 其他设备 上读取到 内存 中的流。
  • 输出流 :把数据从 内存 中写出到 其他设备 上的流。

根据数据的类型分为:字节流和字符流。

  • 字节流 :以字节为单位,读写数据的流。
  • 字符流 :以字符为单位,读写数据的流。

1.3 IO顶级的父类

第二章:字节流

2.1 一切皆为字节流

​ 一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都是一个一个的字节,那么传输时一 样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

2.2 字节输出流-OutputStream

​ java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • 方法
    • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
    • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
    • public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
    • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
    • public abstract void write(int b) :将指定的字节输出流。
  • 注意事项
    • close方法,当完成流的操作时,必须调用此方法,释放系统资源。

2.3 FileOutputStream类

​ OutputStream 有很多子类,我们从最简单的一个子类开始。

java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。

  • 构造方法

    1. public FileOutputStream(File file) :创建文件输出流以写入由指定的 File对象表示的文件。
    2. public FileOutputStream(String name) : 创建文件输出流以指定的名称写入文件。
    3. 注意:
      • 当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。
  • 写出字节数据

        FileOutputStream fos = new FileOutputStream("a.txt");
        //  【write(int b)】
        // 写入的十进制会转换成二进制存储到a.txt
        // 读取文件时,文件会按照指定的编码格式转换为对应的内容
        fos.write(97);  // 写入→97→110 0001‬→内存→读取→110 0001→ASCII→a
        fos.write(98);
        // 【 write(byte[] b)】
        byte[]bs = {97,98,99,101,102};
        fos.write(bs);
        // 【字符串转换字节数组getBytes()】
        fos.write("你好".getBytes());
        // 【write(byte[] b, int off, int len)指定长度的字节数组】
        fos.write("xyz".getBytes(),0,2);
        fos.close();
    
    
  • 数据追加续写

    • 问题

      • 经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能 继续添加新数据呢?
    • 解决方案

      • public FileOutputStream(File file, boolean append) : 创建文件输出流以写入由指定的 File对象表示的文件。
      • public FileOutputStream(String name, boolean append) : 创建文件输出流以指定的名称写入文件。
      • 参数: true 表示追加数据, false 表示清空原有数据。
    • 代码

          FileOutputStream fos = new FileOutputStream("a.txt",true);
          fos.write("我是新追加的数据".getBytes());
          fos.close();
      
  • 写出换行

    • 系统中的换行

      • Windows系统里,每行结尾是 回车+换行 ,即 \r\n
      • Unix系统里,每行结尾只有 换行 ,即 \n
      • Mac系统里,每行结尾是 回车 ,即 \r 。从 Mac OS X开始与Linux统一。
    • 代码

          FileOutputStream fos = new FileOutputStream("b.txt",true);
          for (int i = 0; i < 10; i++) {
            fos.write(("\r\n第" + i +"行数据:" + i*100).getBytes() );
          }
          fos.close();
      

2.4 字节输入流

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入 流的基本共性功能方法。

  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。

  • public abstract int read() : 从输入流读取数据的下一个字节。

  • public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

    • 使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使

      用。

close方法,当完成流的操作时,必须调用此方法,释放系统资源。

2.5 FileInputStream类

java.io.FileInputStream 类是文件输入流,从文件中读取字节。

  • 构造方法

    1. FileInputStream(File file) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系 统中的 File对象 fifile命名。
    2. FileInputStream(String name) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件 系统中的路径名 name命名。
  • 读取字节数据代码

        // 创建字节输入流对象
        FileInputStream fis = new FileInputStream("day07_IO//a.txt");
        //【一次读取一个字节】
        // 循环读取文件中的字节
        int len = 0;
        while ((len=fis.read())!=-1){
          System.out.print((char)len);
        }
        // 结果:ABC
        // 【一次读取多个字节】
        // 将字节转换为字符串new Strig(bytes[]bts,int offset,int length);
        byte[]bts = new byte[10];
        int len2 = 0;
        while ((len2=fis.read(bts))!=-1){
          System.out.println(new String(bts,0,len2));
        }
    	fis.close();
    

2.6 复制图片文件

  • 原理

  • 代码

      public static void main(String[] args) throws IOException {
        long s = System.currentTimeMillis();
        // 创建输入流对象-用来读取本地文件
        FileInputStream fis = new FileInputStream("D:\\test.jpg");
        // 创建输出流对象-用来写入本地文件
        FileOutputStream fos = new FileOutputStream("day07_IO\\test_copy.jpg");
        // 创建字节数组,一次从本地读取多个字节
        byte[]bts = new byte[1024];
        int len = 0; // 表示读取的有效字节个数
        // 循环读取本地数据
        while ((len=fis.read(bts))!=-1){
          // 把实际读取的字节写入本地文件
          fos.write(bts,0,len);
        }
        // 关闭输出流资源
        fos.close();
        // 关闭输入流资源
        fis.close();
        long e = System.currentTimeMillis();
        System.out.println("复制成功!");
        System.out.println("共耗时" + (e-s)+"毫秒");
      }
    

第三章:字符流

3.1 为什么要学习字符流

当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为

一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文

件。

3.2 字符输入流-Reader

  1. public void close() :关闭此流并释放与此流相关联的任何系统资源。
  2. public int read() : 从输入流读取一个字符。
  3. public int read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。

3.3 FileReader类

java.io.FileReader 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区

  • 注意事项

    1. 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。 idea中UTF-8

    2. 字节缓冲区:一个字节数组,用来临时存储字节数据。

  • 构造函数

    1. FileReader(File file) : 创建一个新的 FileReader ,给定要读取的File对象。
    2. FileReader(String fileName) : 创建一个新的 FileReader ,给定要读取的文件的名称。
  • 代码

      public static void main(String[] args) throws IOException {
        FileReader reader = new FileReader("day07_IO//a.txt");
        // 【一次读取一个字符】
        int len = 0;
        while ((len = reader.read())!=-1) {
          System.out.print((char)len);
        }
        // 【一次读取多个字符】
        char[]chs = new char[1024];
        int len = 0;
        while ((len = reader.read(chs))!=-1) {
          System.out.print(new String(chs,0,len));
          // new String(char[]chars,int offset,int count);
        }
        reader.close();
    
      }
    

3.4 字符输出流-Writer

java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节

输出流的基本共性功能方法。

  1. void write(int c) 写入单个字符。
  2. void write(char[] cbuf) 写入字符数组。
  3. abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,offff数组的开始索引,len 写的字符个数。
  4. void write(String str) 写入字符串。
  5. void write(String str, int off, int len) 写入字符串的某一部分,offff字符串的开始索引,len写的字符个 数。
  6. void flush() 刷新该流的缓冲。
  7. void close() 关闭此流,但要先刷新它。

3.5 FileWriter类

java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

  • 构造函数:

    1. FileWriter(File file) : 创建一个新的 FileWriter,给定要读取的File对象。
    2. FileWriter(String fileName) : 创建一个新的 FileWriter,给定要读取的文件的名称。
  • 代码:

      public static void main(String[] args) throws IOException {
        // 创建输出字符流对象
        FileWriter fw = new FileWriter("day07_IO\\b.txt");
        fw.write("你好");
        fw.flush(); // flush将缓冲区的数据刷新到文件中,可继续写入
        fw.write("我好");
        fw.flush();
        fw.write("大家好");
        fw.close();// close 将缓冲区中的数据刷新到文件中,并且会释放输出流对象,后继无法继续输出
        fw.write("真好");   // 异常
          
        // 创建输出流对象,向指定的文件中追加写入数据
        FileWriter fw2 = new FileWriter("day07_IO\\b.txt",true);
        for (int i = 0; i < 10; i++) {
          fw2.write("你好,新的世界!" + i + "\r\n");
        }
        fw2.close();
      }
    
  • 注意事项:

    • 因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要 flush 方法了。
    • flush :刷新缓冲区,流对象可以继续使用。
    • close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

第四章:IO异常处理

4.1 JDK7之前的处理方式

  • 处理方式:try-catch-finally

    • 在finally中释放资源
  • 代码:

      public static void main(String[] args)  {
        // 创建输出流对象,向指定的文件中追加写入数据
        FileWriter fw2 = null;
        try{
          fw2 = new FileWriter("day07_IO\\b.txt",true);
          for (int i = 0; i < 10; i++) {
            fw2.write("你好,新的世界!" + i + "\r\n");
          }
        }catch (IOException e) {
          e.printStackTrace();
        }finally {
          if(fw2!=null){
            try {
              fw2.close();
            } catch (IOException e) {
              e.printStackTrace();
            }
          }
        }
    
      }
    

4.2 JDK7中的新特性

  • 处理方式:JDK7优化后的 try-with-resource 语句,该语句确保了每个资源在语句结束时关闭。所谓的资源 (resource)是指在程序完成后,必须关闭的对象。

    try (创建流对象语句,如果多个,使用';'隔开) { 
        // 读写数据 
    } catch (IOException e) { 
        e.printStackTrace(); 
    }
    
  • 代码:

    public static void main(String[] args)  {
        // 创建输出流对象,向指定的文件中追加写入数据
        try(FileWriter fw2 = new FileWriter("day07_IO\\b.txt",true)){
          for (int i = 0; i < 100; i++) {
            fw2.write("你好,新的世界!" + i + "\r\n");
          }
        }catch (IOException e) {
          e.printStackTrace();
        }
      }
    

4.3 JDK9中的新特性

​ JDK9中 try-with-resource 的改进,对于引入对象的方式,支持的更加简洁。被引入的对象,同样可以自动关闭, 无需手动close

  • 格式

    // 被final修饰的对象 
    final Resource resource1 = new Resource("resource1"); 
    // 普通对象 
    Resource resource2 = new Resource("resource2"); 
    // 引入方式:直接引入 
    try (resource1; resource2) { 
        // 使用对象 
    } catch (IOException e) { 
        e.printStackTrace(); 
    }
    
  • 代码

    public static void main(String[] args) throws IOException  {
        // 创建输出流对象,向指定的文件中追加写入数据
        FileWriter fw2 = new FileWriter("day07_IO\\b.txt",true);
        try(fw2){
          for (int i = 0; i < 100; i++) {
            fw2.write("你好,新的世界!" + i + "\r\n");
          }
        }catch (IOException e) {
          e.printStackTrace();
        }
      }
    

第五章:属性集

5.1 属性集合介绍

java.util.Properties 继承于 Hashtable ,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其

对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时, System.getProperties 方法就是返回

一个 Properties 对象。

5.2 Properties 类

  • 构造方法public Properties() :创建一个空的属性列表。

  • 基本的存储方法

    1. public Object setProperty(String key, String value) : 保存一对属性。
    2. public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。
    3. public Set<String> stringPropertyNames() :所有键的名称的集合。
  • 与流相关的方法

    1. public void load(InputStream inStream) : 从字节输入流中读取键值对。
      • 注意:文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。
  • 代码

    public static void main(String[] args) throws IOException {
        // 获取系统相关的信息
        System.out.println(System.getProperties());
        // 创建一个属性集合
        Properties pro = new Properties();
        pro.setProperty("张三","180");
        pro.setProperty("李四","189");
        pro.setProperty("王五","178");
        pro.setProperty("赵六","188");
        // 读取属性集合中的数据
        Set set = pro.keySet();
        for (Object o : set) {
          System.out.println(pro.get(o));
        }
        // 把集合中临时的数据输出到硬盘中
        // pro.store(new FileWriter("day07_IO\\data.txt"),"save data");
    
        // 读取本地文件中的数据(键值对的方式)到属性集合中
        Properties pro2 = new Properties();
        pro2.load(new FileReader("day07_IO\\data.txt"));
        System.out.println(pro2);
      }
    

第六章:缓冲流

6.1 缓冲流介绍

缓冲流,也叫高效流,是对4个基本的 FileXxx 流的增强,所以也是4个流,按照数据类型分类:

  • 字节缓冲流: BufferedInputStream , BufferedOutputStream
  • 字符缓冲流: BufferedReader , BufferedWriter

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

6.2 字节缓冲流

  • 构造方法

    1. public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。
    2. public BufferedOutputStream(OutputStream out) : 创建一个新的缓冲输出流。
  • 代码测试

    1. 基本方式一次读写一个字节

      public static void main(String[] args) throws IOException {
          long s = System.currentTimeMillis();
          // 创建字节输入流,读取本地文件数据
          FileInputStream fis = new FileInputStream("D:\\test.jpg");
          // 创建字节输出流,向本地文件写入数据
          FileOutputStream fos = new FileOutputStream("day08_IO\\test01.jpg");
          // 定义变量表示读取的字节
          int len = 0;
          // 循环遍历读取本地文件
          while ((len=fis.read())!=-1) {
            // 把读取的字节写入本地
            fos.write(len);
          }
          // 关闭输出流
          fos.close();
          // 关闭输入流
          fis.close();
          long e = System.currentTimeMillis();
          System.out.println("共耗时:" + (e-s) + "毫秒"); // 161毫秒
      
        }
      
    2. 基本方式一次读写多个字节

      public static void main(String[] args) throws IOException {
          long s = System.currentTimeMillis();
          // 创建字节输入流,读取本地文件数据
          FileInputStream fis = new FileInputStream("D:\\test.jpg");
          // 创建字节输出流,向本地文件写入数据
          FileOutputStream fos = new FileOutputStream("day08_IO\\test02.jpg");
          // 定义变量表示读取的有序字节个数
          int len = 0;
          // 定义字节数组,表示一次要读取的字节数
          byte[]bts = new byte[1024];
          // 循环遍历读取本地文件
          while ((len=fis.read(bts))!=-1) {
            // 把读取的字节写入本地
            fos.write(bts,0,len);
          }
          // 关闭输出流
          fos.close();
          // 关闭输入流
          fis.close();
          long e = System.currentTimeMillis();
          System.out.println("共耗时:" + (e-s) + "毫秒"); // 共耗时:7毫秒
      
        }
      
    3. 缓冲流方式默认读写方式

      public static void main(String[] args) throws IOException {
          long s = System.currentTimeMillis();
          // 创建字节缓冲流输入流
          BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\test.jpg"));
          // 创建字节缓冲流输出流f
          BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("day08_IO\\test03.jpg"));
          // 循环读取本地数据
          int len = 0;
          while ((len = bis.read())!=-1){
            bos.write(len);
          }
          bos.close();
          bis.close();
          long e = System.currentTimeMillis();
          System.out.println("共耗时:" + (e-s) + "毫秒"); // 共耗时:8毫秒
        }
      
    4. 缓冲流方式指定字节读取

      public static void main(String[] args) throws IOException {
          long s = System.currentTimeMillis();
          // 创建字节缓冲流输入流
          BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\test.jpg"));
          // 创建字节缓冲流输出流f
          BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("day08_IO\\test04.jpg"));
          // 循环读取本地数据
          int len = 0;
          byte[]bts = new byte[1024];
          while ((len = bis.read(bts))!=-1){
            bos.write(bts,0,len);
          }
          bos.close();
          bis.close();
          long e = System.currentTimeMillis();
          System.out.println("共耗时:" + (e-s) + "毫秒"); // 共耗时:6毫秒
        }
      

6.3 字符缓冲流

  • 构造方法

    1. public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
    2. public BufferedWriter(Writer out) : 创建一个新的缓冲输出流。
  • 特有方法:字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。

    1. BufffferedReader: public String readLine() : 读一行文字。
    2. BufffferedWriter: public void newLine() : 写一行行分隔符,由系统属性定义符号。
  • 代码:

    public static void main(String[] args) throws IOException {
        // 创建字符缓冲输出流
        BufferedWriter bw = new BufferedWriter( new FileWriter("day08_IO\\01.txt"));
        for (int i = 0; i < 100; i++) {
          bw.write("贵在坚持!不只是说说!" + i);
          bw.newLine();
        }
        bw.close();
        // 创建字符缓冲输入流
        BufferedReader br = new BufferedReader(new FileReader("day08_IO\\01.txt"));
        String line;
        while ((line=br.readLine())!=null){
          System.out.println(line);
        }
        br.close();
      }
    

6.4 文本排序

  • 需求:读取本地文件中的文本,并排序后重新写入本地新的文件中

    2.JavaScript
    1.Java
    4.PHP
    6.C#
    7.C
    3.C++
    5.VB
    
  • 代码:

     public static void main(String[] args) throws IOException {
        // 创建HashMap对象存放读取的本地数据
        HashMap<String,String > map = new HashMap<>();
        // 创建字符缓冲区输入流,逐行读取本地数据
        BufferedReader br = new BufferedReader(new FileReader("day08_IO\\02.txt"));
        String line;
        while ((line=br.readLine())!=null) {
          // 分割
          String[]strs = line.split("\\.");
          map.put(strs[0],strs[1]);
        }
        br.close();
        // 创建字符输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("day08_IO\\03.txt"));
        for (int i = 1; i <= map.size(); i++) {
          bw.write(i +"."+ map.get(String.valueOf(i)));
          bw.newLine();
        }
        bw.close();
    
      }
    

第七章:转换流

7.1 字符编码和字符集

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

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

  • 字符集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-5559-1使用单字节编码,兼容ASCII编码。
    • GBxxx字符集

    • GB就是国标的意思,是为了显示中文而设计的一套字符集。

    • GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。

    • GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了 21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。

    • 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辅助字符,使用四字节编码。

7.2 编码引出的问题

​ 在IDEA中,使用 FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的 UTF-8 编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。

public static void main(String[] args) throws IOException {
    FileReader fr = new FileReader("D:\\a.txt");
    int len = 0;
    while((len=fr.read())!=-1){
      System.out.print((char)len); 
    }
    fr.close();
  }
// 结果:��� 乱码

那么如何读取GBK编码的文件呢?

7.3 InputStreamReader类

​ 转换流 java.io.InputStreamReader ,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

  • 构造方法

    1. InputStreamReader(InputStream in) : 创建一个使用默认字符集的字符流。
    2. InputStreamReader(InputStream in, String charsetName) : 创建一个指定字符集的字符流。
  • 代码

    public static void main(String[] args) throws IOException {
        InputStreamReader fr = new InputStreamReader(new FileInputStream("D:\\a.txt"),"gbk");
        int len = 0;
        while((len=fr.read())!=-1){
          System.out.print((char)len);
        }
        fr.close();
      }
    // 结果:你好
    

7.4 OutputStreamWriter 类

​ 转换流 java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符 编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。

  • 构造函数

    1. OutputStreamWriter(OutputStream in) : 创建一个使用默认字符集的字符流。
    2. OutputStreamWriter(OutputStream in, String charsetName) : 创建一个指定字符集的字符流。
  • 代码

      public static void main(String[] args) throws IOException {
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("day08_IO\\04.txt"),"gbk");
        osw.write("你好");
        osw.close();
      }
    

7.5 图解转换流原理

第八章:序列化

8.1 对象序列化介绍

​ Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该 对象的数据 、 对象的 类型 和 对象中存储的属性 等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。 对象的数据 、 对象的类型 和 对象中 存储的数据 信息,都可以用来在内存中创建对象。看图理解序列化:

8.2 ObjectOutputStream类

java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

  • 构造方法

    1. public ObjectOutputStream(OutputStream out) : 创建一个指定OutputStream的ObjectOutputStream。
  • 序列化操作

    1. 一个对象要想序列化,必须满足两个条件:

      • 该类必须实现 java.io.Serializable 接口, Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException

      • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用 transient 关键字修饰。

      • 代码:

        public class Person implements Serializable { // 实现Serializable,表示可序列化
          private String name;
          private int age;
          private transient int height;  // 该属性不会被序列化
          public Person(String name, int age, int height) {
            this.name = name;
            this.age = age;
            this.height = height;
          }
          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;
          }
          public int getHeight() {
            return height;
          }
          public void setHeight(int height) {
            this.height = height;
          }
          @Override
          public String toString() {
            return "姓名:" + name + ",年龄:"+ age + ",身高:" + height;
          }
        }
        
        
    2. **写出对象方法 **

      • public final void writeObject (Object obj) : 将指定的对象写出

      • 代码

        public static void main(String[] args) throws IOException {
            // 创建一个人类对象
            Person p = new Person("张三",18,180);
            // 创建一个序列化输出流对象
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("day08_IO\\person.txt"));
            // 把指定的对象序列化后写入本地文件
            os.writeObject(p);
            os.close();
            System.out.println("save a Object success");
          }
        

8.3 ObjectInputStream类

​ ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

  • 构造方法

  • public ObjectInputStream(InputStream in); : 创建一个指定InputStream的ObjectInputStream。

  • 反序列化操作1

    • 如果能找到一个对象的class文件,我们可以进行反序列化操作,调用 ObjectInputStream 读取对象的方法:

      • public final Object readObject () : 读取一个对象。
    • 代码:

      public static void main(String[] args) throws IOException, ClassNotFoundException {
          ObjectInputStream os = new ObjectInputStream(new FileInputStream("day08_IO\\person.txt"));
          Person p = (Person) os.readObject();
          System.out.println(p); // 姓名:张三,年龄:18,身高:0
          os.close();
        }
      
      // 对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个ClassNotFoundException 异常。
      
  • 反序列化操作2

    • 另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操 作也会失败,抛出一个 InvalidClassException 异常。发生这个异常的原因如下:

      • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
      • 该类包含未知数据类型
      • 该类没有可访问的无参数构造方法
    • 解决方案

      • Serializable 接口给需要序列化的类,提供了一个序列版本号。 serialVersionUID 该版本号的目的在于验证序 列化的对象和对应类是否版本匹配。
    • 代码

      public class Person implements Serializable { // 实现Serializable,表示可序列化
        // 定义序列化版本号
        private static final long serialVersionUID = 10L;
        private String name;
        public int age;
        private transient int height;  // 该属性不会被序列化
        public Person(String name, int age, int height) {
          this.name = name;
          this.age = age;
          this.height = height;
        }
        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;
        }
        public int getHeight() {
          return height;
        }
        public void setHeight(int height) {
          this.height = height;
        }
        @Override
        public String toString() {
          return "姓名:" + name + ",年龄:"+ age + ",身高:" + height;
        }
      }
      
      

第九章:打印流

9.1 打印流介绍

​ 平时我们在控制台打印输出,是调用 print 方法和 println 方法完成的,这两个方法都来自于 java.io.PrintStream 类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。

9.2 PrintStream类

  • 构造方法

    • public PrintStream(String fileName) : 使用指定的文件名创建一个新的打印流。
  • System.out 就是 PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象, 我们就可以玩一个"小把戏",改变它的流向。

  • 代码

      public static void main(String[] args) throws IOException {
        // 打印在控制台
        System.out.println("你好");
        // 创建打印流,流向指定的本地文件
        PrintStream ps = new PrintStream(new FileOutputStream("day08_IO\\print.txt"));
        // 设置System.out的打印流向指向ps
        System.setOut(ps);
        // 后续打印在print.txt文件中
        System.out.println("你好1");
        System.out.println("你好2");
        System.out.println("你好3");
        ps.close();
      }
    
posted @ 2019-12-09 12:32  雷哒哒  阅读(202)  评论(0编辑  收藏  举报