打怪升级之小白的大数据之旅(二十四)<Java面向对象进阶之IO流二 字节流与字符流>

打怪升级之小白的大数据之旅(二十四)

Java面向对象进阶之IO流二 字节流与字符流

上次回顾

上一章介绍了IO流的基本概念以及FIle类与IO的基类,本章将对最常使用的两大流字节流与字符流进行讲解,今天内容结束后,我们就可以手撸一个复制视频/图片/音频的小程序啦…

字节流

  • 字节流就跟它的名字一样,它的存储格式是以字节进行存储的,它将一切文件数据(文本、图片、视频、音频)等,全部以二进制数字的形式保存
  • 同样的,在数据传输时,都是一个一个字节进行传输,所以,字节流可以传输任意的文件数据
  • 字节流的操作分为字节输出流和字节输入流

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()方法,释放系统资源,不调用的后果,我等下会提到
  • OutputStream有很多子类,这里我们就介绍一个FileOutputStream,其他的子类操作基本雷同

FileOutputStream

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

当创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据

构造方法

  • public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件
  • 示例代码:
    public class FileOutputStreamConstructor throws IOException {
        public static void main(String[] args) {
       	 	// 使用File对象创建流对象
            File file = new File("a.txt");
            FileOutputStream fos = new FileOutputStream(file);
          
            // 使用文件名称创建流对象
            FileOutputStream fos = new FileOutputStream("b.txt");
        }
    }
    

输出字节数据

  • 写出字节:write(int b) 方法,每次可以写出一个字节数据
    • 示例代码:
      public class FOSWrite {
          public static void main(String[] args) throws IOException {
              // 使用文件名称创建流对象
              FileOutputStream fos = new FileOutputStream("fos.txt");     
            	// 写出数据
            	fos.write(97); // 写出第1个字节
            	fos.write(98); // 写出第2个字节
            	fos.write(99); // 写出第3个字节
            	// 关闭资源
              fos.close();
          }
      }
      
  • 写出字节数组:write(byte[] b),每次可以写出数组中的数据
    • 示例代码:
      public class FOSWrite {
          public static void main(String[] args) throws IOException {
              // 使用文件名称创建流对象
              FileOutputStream fos = new FileOutputStream("fos.txt");     
            	// 字符串转换为字节数组
            	byte[] b = "大数据".getBytes();
            	// 写出字节数组数据
            	fos.write(b);
            	// 关闭资源
              fos.close();
          }
      }
      
  • 写出指定长度字节数组:write(byte[] b, int off, int len) ,每次写出从off索引开始,len个字节
    • 示例代码:
      public class FOSWrite {
          public static void main(String[] args) throws IOException {
              // 使用文件名称创建流对象
              FileOutputStream fos = new FileOutputStream("fos.txt");     
            	// 字符串转换为字节数组
            	byte[] b = "abcde".getBytes();
      		// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
              fos.write(b,2,2);
            	// 关闭资源
              fos.close();
          }
      }
      
  • 上面的示例中,小伙伴们有没有发现,我们每次运行,都会覆盖掉前面的结果,那么怎么才能不覆盖结果而是追加呢?
    • public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。
    • public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件
    • 这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了
    • 示例代码:
      public class FOSWrite {
          public static void main(String[] args) throws IOException {
              // 使用文件名称创建流对象
              FileOutputStream fos = new FileOutputStream("fos.txt"true);     
            	// 字符串转换为字节数组
            	byte[] b = "abcde".getBytes();
      		// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
              fos.write(b);
            	// 关闭资源
              fos.close();
          }
      }
      

InputStream字节输入流

  • 介绍完输出流,接下来,我来介绍输入流,和输出流一样,InputStream也是输入流的基类
  • 它的共有方法如下:
    • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
    • public abstract int read(): 从输入流读取数据的下一个字节。
    • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中
    • 同样的,完成流的操作后,需要调用close()方法进行释放资源

FileInputStream类

我依旧使用其中一个子类来说明输入流的用法
java.io.FileInputStream类是文件输入流,从文件中读取字节

构造方法

  • FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
  • FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名
  • 示例代码:
    public class FileInputStreamConstructor throws IOException{
        public static void main(String[] args) {
       	 	// 使用File对象创建流对象
            File file = new File("a.txt");
            FileInputStream fis = new FileInputStream(file);
          
            // 使用文件名称创建流对象
            FileInputStream fis = new FileInputStream("b.txt");
        }
    }
    

读取字节数据

  • 读取字节read()方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1
    • 示例代码:
      public class FISRead {
          public static void main(String[] args) throws IOException{
            	// 使用文件名称创建流对象
             	FileInputStream fis = new FileInputStream("read.txt");
            	// 读取数据,返回一个字节
              int read = fis.read();
              System.out.println((char) read);
              read = fis.read();
              System.out.println((char) read);
              read = fis.read();
              System.out.println((char) read);
              read = fis.read();
              System.out.println((char) read);
              read = fis.read();
              System.out.println((char) read);
            	// 读取到末尾,返回-1
             	read = fis.read();
              System.out.println( read);
      		// 关闭资源
              fis.close();
          }
      }
      public class FISRead {
          public static void main(String[] args) throws IOException{
            	// 使用文件名称创建流对象
             	FileInputStream fis = new FileInputStream("read.txt");
            	// 定义变量,保存数据
              int b ;
              // 循环读取
              while ((b = fis.read())!=-1) {
                  System.out.println((char)b);
              }
      		// 关闭资源
              fis.close();
          }
      }
      
  • 使用字节数组读取:read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1
    • 示例代码:
      public class FISRead {
          public static void main(String[] args) throws IOException{
            	// 使用文件名称创建流对象.
             	FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
            	// 定义变量,作为有效个数
              int len ;
              // 定义字节数组,作为装字节数据的容器   
              byte[] b = new byte[2];
              // 循环读取
              while (( len= fis.read(b))!=-1) {
                 	// 每次读取后,把数组变成字符串打印
                  System.out.println(new String(b));
              }
      		// 关闭资源
              fis.close();
          }
      }
      
    • 在上面的示例中,会出现错误数据d,是由于最后一次读取时,只读取一个字节e,数组中,上次读取的数据没有被完全替换,所以要通过len ,获取有效的字节,正确的写法如下:
      public class FISRead {
          public static void main(String[] args) throws IOException{
            	// 使用文件名称创建流对象.
             	FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
            	// 定义变量,作为有效个数
              int len ;
              // 定义字节数组,作为装字节数据的容器   
              byte[] b = new byte[2];
              // 循环读取
              while (( len= fis.read(b))!=-1) {
                 	// 每次读取后,把数组的有效字节部分,变成字符串打印
                  System.out.println(new String(b,0,len));//  len 每次读取的有效字节个数
              }
      		// 关闭资源
              fis.close();
          }
      }
      

字节流练习

为了熟悉字节流的相关操作,我为大家提供一个小练习;就是对图片的复制,同样的,先不要看我后面的示例代码,先自己写,我来说明一下复制文件的基本原理,如图:在这里插入图片描述
我写了一个图片复制的小demo,不是特别严谨哈,异常捕获依旧主动抛出的

public class Copy {
    public static void main(String[] args) throws IOException {
        // 1.创建流对象
        // 1.1 指定数据源
        FileInputStream fis = new FileInputStream("D:\\test.jpg");
        // 1.2 指定目的地
        FileOutputStream fos = new FileOutputStream("test_copy.jpg");

        // 2.读写数据
        // 2.1 定义数组
        byte[] b = new byte[1024];
        // 2.2 定义长度
        int len;
        // 2.3 循环读取
        while ((len = fis.read(b))!=-1) {
            // 2.4 写出数据
            fos.write(b, 0 , len);
        }

        // 3.关闭资源
        fos.close();
        fis.close();
    }
}

流的关闭原则:先开后关,后开先关

完整的图片复制代码以及新的异常捕获

好了,练习介绍完了,我为大家介绍一下完整的异常写法,写完就知道我为啥偷懒了…还是以复制图片为例子:

package com.test02iotest;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) {
        // 定义图片目录
        String filePath = "F:\\图片目录";

        // 定义输入输出流
        FileInputStream file=null;
        FileOutputStream file2=null;

        // 异常捕获
        try {
            // 创建输入/输出对象
            file  = new FileInputStream(filePath+"\\xxx.jpg");
            file2  = new FileOutputStream("xxxCopy.jpg");
            // 定义字节数组
            byte[] byt = new byte[1024];
            // 获取文件的总长度
            int len;
            // 开始循环读取文件
            while ((len = file.read(byt))!=-1){
                // 对读取的文件进行复制写出
                file2.write(byt,0,len);
            }
        }catch (FileNotFoundException e){
            // 捕获文件不存在的异常
            System.out.println("文件不存在");
        } catch (IOException e) {
            // 文件读取,写出和其他的异常
            e.printStackTrace();
        } finally {
            // 释放资源,因为前面初始化该文件的值为null,因此需要判断
            if (file!=null) {
                try {
                    file.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (file2!=null){
                try {
                    file2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

是不是感觉特别麻烦,就一个close资源释放,都需要这么繁琐?这就是我偷懒的原因,在jdk1.7之后对IO异常的操作有了新的写法
语法格式:

try(需要关闭的资源对象的声明){
    业务逻辑代码,可能发生异常的代码
}catch(异常类型 e){
    处理异常代码
}catch(异常类型 e){
    处理异常代码
}

下面我根据新的异常方法,对上面的图片复制进行改进

package com.test02iotest;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo2 {
    public static void main(String[] args) {
        // 定义图片目录
        String filePath = "F:\\图片目录";

        // 异常捕获
        try (// 定义输入输出流
             FileInputStream file = new FileInputStream(filePath + "\\xxx.jpg");
             FileOutputStream file2 = new FileOutputStream("xxxCopy.jpg");
             ){
            // 定义字节数组
            byte[] byt = new byte[1024];
            // 获取文件的总长度
            int len;
            // 开始循环读取文件
            while ((len = file.read(byt))!=-1){
                // 对读取的文件进行复制写出
                file2.write(byt,0,len);
            }
        }catch (FileNotFoundException e){
            // 捕获文件不存在异常
            System.out.println("文件不存在");
        }catch (IOException e){
            // 文件读取,写出和其他的异常
            e.printStackTrace();
        }
    }
}

好了,字节流的用法和异常就介绍到这里,哦,我下面的代码依旧偷懒了…

字符流

  • 既然字节流都可以存取任意的数据了,那为什么还要有字符流呢?
  • 那就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件

Reader字符输入流

  • 触类旁通,介绍完字节流的输入与输出,字符流的相关操作跟它一样,首先介绍字符流的基类Reader
  • java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法:
    • public void close() :关闭此流并释放与此流相关联的任何系统资源。
    • public int read(): 从输入流读取一个字符。
    • public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中

FileReader类

  • java.io.FileReader类是读取字符文件的子类。构造时使用系统默认的字符编码和默认字节缓冲区
  • 等会讲到转换流的时候,我会讲解字符编码问题

构造方法

  • FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
  • FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称
  • 示例代码:
    public class FileReaderConstructor throws IOException{
        public static void main(String[] args) {
       	 	// 使用File对象创建流对象
            File file = new File("a.txt");
            FileReader fr = new FileReader(file);
          
            // 使用文件名称创建流对象
            FileReader fr = new FileReader("b.txt");
        }
    }
    

读取字符数据

  • 读取字符: read()方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1,循环读取,示例代码如下:
    public class FRRead {
        public static void main(String[] args) throws IOException {
          	// 使用文件名称创建流对象
           	FileReader fr = new FileReader("read.txt");
          	// 定义变量,保存数据
            int b ;
            // 循环读取
            while ((b = fr.read())!=-1) {
                System.out.println((char)b);
            }
    		// 关闭资源
            fr.close();
        }
    }
    
  • 读取字符数组:read(char[] cbuf),每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1
    • 示例代码:
      public class FRRead {
          public static void main(String[] args) throws IOException {
            	// 使用文件名称创建流对象
             	FileReader fr = new FileReader("read.txt");
            	// 定义变量,保存有效字符个数
              int len ;
              // 定义字符数组,作为装字符数据的容器
               char[] cbuf = new char[2];
              // 循环读取
              while ((len = fr.read(cbuf))!=-1) {
                  System.out.println(new String(cbuf));
              }
      		// 关闭资源
              fr.close();
          }
      }
      
    • 同样的,我们使用字符数组读取时,需要对其进行改进:
      public class FISRead {
          public static void main(String[] args) throws IOException {
            	// 使用文件名称创建流对象
             	FileReader fr = new FileReader("read.txt");
            	// 定义变量,保存有效字符个数
              int len ;
              // 定义字符数组,作为装字符数据的容器
              char[] cbuf = new char[2];
              // 循环读取
              while ((len = fr.read(cbuf))!=-1) {
                  System.out.println(new String(cbuf,0,len));
              }
          	// 关闭资源
              fr.close();
          }
      }
      

Writer字符输出流

  • 大家是不是闭着眼睛都知道我下面内容是什么了?没错,介绍基类和共有方法,然后使用子类和介绍构造方法,接着使用单个,数组等举例…
  • java.io.Writer抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法:
    • void write(int c) 写入单个字符。
    • void write(char[] cbuf)写入字符数组。
    • abstract void write(char[] cbuf, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
    • void write(String str)写入字符串。
    • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
    • void flush()刷新该流的缓冲。
    • void close() 关闭此流,但要先刷新它

FileWriter类

  • java.io.FileWriter类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区
  • 咦?有一个新的知识点,默认字节缓冲区?卖个关子,我在缓冲流为大家介绍这个内容

构造方法

  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称
  • 示例代码:
    public class FileWriterConstructor {
        public static void main(String[] args) throws IOException {
       	 	// 使用File对象创建流对象
            File file = new File("a.txt");
            FileWriter fw = new FileWriter(file);
          
            // 使用文件名称创建流对象
            FileWriter fw = new FileWriter("b.txt");
        }
    }
    

写出数据

  • 写出字符:write(int b) 方法,每次可以写出一个字符数据,示例代码如下:
    public class FWWrite {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建流对象
            FileWriter fw = new FileWriter("fw.txt");     
          	// 写出数据
          	fw.write(97); // 写出第1个字符
          	fw.write('b'); // 写出第2个字符
          	fw.write('C'); // 写出第3个字符
          	fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。
          
          	/*
            【注意】关闭资源时,与FileOutputStream不同。
          	 如果不关闭,数据只是保存到缓冲区,并未保存到文件。
            */
            // fw.close();
        }
    }
    
  • 写出字符数组:write(char[] cbuf)write(char[] cbuf, int off, int len) ,每次可以写出字符数组中的数据,用法类似FileOutputStream,示例代码如下:
    public class FWWrite {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建流对象
            FileWriter fw = new FileWriter("fw.txt");     
          	// 字符串转换为字节数组
          	char[] chars = "尚硅谷".toCharArray();
          
          	// 写出字符数组
          	fw.write(chars); // 尚硅谷
            
    		// 写出从索引1开始,2个字节。索引1是'硅',两个字节,也就是'硅谷'。
            fw.write(b,1,2); // 硅谷
          
          	// 关闭资源
            fos.close();
        }
    }
    
  • 写出字符串:write(String str)write(String str, int off, int len) ,每次可以写出字符串中的数据,更为方便,示例代码如下:
    public class FWWrite {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建流对象
            FileWriter fw = new FileWriter("fw.txt");     
          	// 字符串
          	String msg = "尚硅谷";
          
          	// 写出字符数组
          	fw.write(msg); //尚硅谷
          
    		// 写出从索引1开始,2个字节。索引1是'硅',两个字节,也就是'硅谷'。
            fw.write(msg,1,2);	// 尚硅谷
          	
            // 关闭资源
            fos.close();
        }
    }
    
  • 续写和换行: 根字节流一样,需要添加一个参数true,示例代码如下:
    public class FWWrite {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建流对象,可以续写数据
            FileWriter fw = new FileWriter("fw.txt"true);     
          	// 写出字符串
            fw.write("尚");
          	// 写出换行
          	fw.write("\r\n");
          	// 写出字符串
      		fw.write("硅谷");
          	// 关闭资源
            fw.close();
        }
    }
    

关闭和刷新

  • 想了想,还是先铺垫一下再介绍缓冲流,会更好接受这个知识点…

  • 我在上一章介绍IO的共有方法时说过,每次流的操作都需要进行close()释放资源

  • 这是因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了

  • flush :刷新缓冲区,流对象可以继续使用。字符流默认缓冲区为8k的字符数组。

  • close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了

  • 示例代码:

    public class FWWrite {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建流对象
            FileWriter fw = new FileWriter("fw.txt");
            // 写出数据,通过flush
            fw.write('刷'); // 写出第1个字符
            fw.flush();
            fw.write('新'); // 继续写出第2个字符,写出成功
            fw.flush();
          
          	// 写出数据,通过close
            fw.write('关'); // 写出第1个字符
            fw.close();
            fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
            fw.close();
        }
    }
    

总结

  • 字节流是对任意数据操作的流,因为字节流是以字节对数据进行保存的,当存储文本数据时,可能会出现乱码(中文),因为中文是3个字节,而字节流只会存储2个字节
  • 因此,需要在文件中保存数据时,通常使用字节流,当数据中是纯文本的时候,就使用字符流
  • 字节流应用很广泛,我们只要会使用并独立完成对图片进行复制的那个练习,IO流的常用操作知识点基本上就掌握了…好了,下一章,我会对IO的剩下知识点进行分享.
posted @ 2021-04-21 18:10  数据民工  阅读(11)  评论(0)    收藏  举报