18. 基础IO(2)——IO流

Java
283
0
0
2022-11-28

1. 字节流,字符流基本概念

File类不支持文件内容处理,如果要处理文件内容,必须要通过流的操作模式来完成

在java.io包中,流分为两种:字节流与字符流

字节流:读写数据以字节为基本单位(处理二进制文件/数据的时候使用) 字符流:独写数据以字符为基本单位(处理文本文档/数据的时候使用)

2. 字节流

1)InputStream:(输入)把输入设备读取到内存中 2)OutputStream:(输出)把内存中的数据写入到输出设备中

a)FileInputStream和FileOutputStream

FileInputStream 从文件系统中的某个文件中获得输入字节。 FileInputStream 用于读取诸如图像数据之类的原始字节流。

示例:复制图片 1)初阶:

package dqy0305;

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

public class IODemo2 {
    //实现文件的复制 
    public static void main(String[] args) throws IOException {
        copyFile("D:/file_dir/1010.png","D:/file_dir/10102.png");
    }

    private static void copyFile(String srcPath, String destPath) throws IOException {
        //1.先打开文件(先创建InputStream和OutputStreaam对象的过程) 
        FileInputStream fileInputStream = new FileInputStream(srcPath);
        FileOutputStream fileOutputStream = new FileOutputStream(destPath);
        //2.需要读取srcPath文件对应的内容 
        byte[] buffer = new byte[1024];
        int len = -1;
        //如果已经读完了就返回-1.如果返回一个正数,就相当于一共有这个整数的长度 
        while((len = fileInputStream.read(buffer)) != -1){
            //读取成功 
            //3.把读取到的内容写入desrPath对应的文件中
            fileOutputStream.write(buffer,0,len);
        }
        //4.close关闭文件,如果没有关闭就会造成文件资源泄露
        fileInputStream.close();
        fileOutputStream.close();
    }
}

这时对应的文件里就已经复制好了一个图片

img

以上方法有可能正在执行过程中触发了异常,那么还是无法执行close(),并且在实例化fileInputStream和fileOutputStream的时候,如果指定位置的文件不存在,就会导致实例化失败,也就是说还是null,这时在调用close方法的时候就会触发空指针异常,也就有了以下的改进版本:

2)改进版

  public static void main(String[] args) throws IOException {
        copyFile2("D:/file_dir/1010.png","D:/file_dir/10102.png");
    }

private static void copyFile2(String srcPath, String destPath){
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;

        try {
            //1.先打开文件(先创建InputStream和OutputStreaam对象的过程)
            fileInputStream = new FileInputStream(srcPath);
            //但是如果路径对应的文件不存在,此时构造方法也会抛出异常,说明构造尚未成功,还是null, 
            // 那么接下来调用close方法就会触发空指针异常
            fileOutputStream = new FileOutputStream(destPath);
            //2.需要读取srcPath文件对应的内容 
            byte[] buffer = new byte[1024];
            int len = -1;
            //如果已经读完了就返回-1.如果返回一个正数,就相当于一共有这个整数的长度 
            while((len = fileInputStream.read(buffer)) != -1){
                //读取成功 
                //3.把读取到的内容写入desrPath对应的文件中
                fileOutputStream.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //4.close关闭文件,如果没有关闭就会造成文件资源泄露 
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

但是以上代码在异常处理方面过于冗杂,为了提高代码的可读性,就有了以下版本: 3)常用版

  public static void main(String[] args) throws IOException {
        copyFile3();
    }

  private static void copyFile3(){
        //当代码写成这个样子的时候,就不需要显示调用close 
        //try语句会在代码执行完毕后,自动调用close方法(前提是这个类必须实现closeable接口) 
        try(FileInputStream fileInputStream = new FileInputStream("D:/file_dir/1010.png");
            FileOutputStream fileOutputStream = new FileOutputStream("D:/file_dir/10102.png")){
            byte[] buffer = new byte[1024];
            int len = -1;
            while ((len = fileInputStream.read(buffer)) != -1){
                fileOutputStream .write(buffer,0,len);
            }
        }catch (IOException e){
            e.printStackTrace();
        }

    }

b)字节缓冲流 BufferedInputStream 和 BufferedOutputStream(内置了缓冲区)

问题一:为什么需要有缓冲区?

答:当我们用read()读取文件时,每读一个字节,访问一次磁盘,效率很低 。文件过大时,操作起来也不是很方便。因此我们需要用到buffer缓存流,当创建buffer对象时,会创建一个缓冲区数组。当我们读一个文件时,先从磁盘中读到缓冲区,然后直接从缓冲区输出即可,效率会更高

实例:复制图片 1)初版

public class IODemo3 {
    public static void main(String[] args) throws IOException {
          copyFile();
    }

    private static void copyFile() throws IOException {
        //需要创建的实例 BufferedInputStream 和 BufferedOutputStream,就先要创建FileInputStream和FileOutputStream 
        FileInputStream fileInputStream = new FileInputStream("D:/file_dir/1010.png");
        FileOutputStream fileOutputStream = new FileOutputStream("D:/file_dir/10102.png");
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
        byte[] buffer = new byte[1024];
        int length = -1;
        while ((length = bufferedInputStream.read(buffer)) != -1){
            bufferedOutputStream.write(buffer,0,length);
        }
        //有四个流对象,只需要关闭这两个,然后就会自动关闭包含的FileInputStream和FileOutputStream
        bufferedInputStream.close();
        bufferedOutputStream.close();
    }
}

2)改进版

 private static void copyFile2() {
        try( BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("D:/file_dir/1010.png"));
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( new FileOutputStream("D:/file_dir/10102.png"))) {

            byte[] buffer = new byte[1024];
            int length = -1;
            while ((length = bufferedInputStream.read(buffer)) != -1){
                bufferedOutputStream.write(buffer,0,length);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

c)带缓冲区和不带缓冲区的对比

    private static void testNoBuffer() {
        long beg = System.currentTimeMillis();
       try ( FileInputStream fileInputStream = new FileInputStream("D:/file_dir/1010.png")){
           int ch = -1;
           while ((ch = fileInputStream.read()) != -1){

           }
       } catch (IOException e) {
           e.printStackTrace();
       }
       long end = System.currentTimeMillis();
        System.out.println("no buffer:"+(end-beg)+"ms");
    }

    private static void testBuffer() {
        long beg = System.currentTimeMillis();
        try (BufferedInputStream bufferedInputStream= new BufferedInputStream(new FileInputStream("D:/file_dir/1010.png"))){
            int ch = -1;
            while ((ch = bufferedInputStream.read()) != -1){

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("buffer:"+(end-beg)+"ms");

    }

执行主函数,就会发现,读取一个15MB的文件,不带缓冲区大概花了6s左右,但是带缓冲区大概只花了50ms

3. 字符流

1)Reader:(输入)把输入设备读取到内存中 2)Writer:(输出)把内存中的数据写入到输出设备中

字符流与字节流用法基本一致,一个典型的区别是: 字节流的读写操作以byte为单位,缓冲区就是byte[] 字符流的读写操作以char为单位,缓冲区就是char[]

示例: 1)不带缓冲区

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

//字符流:可以来处理文本文件
public class IODemo5 {
    public static void main(String[] args) {
        copyFile();
    }
    
    private static void copyFile(){
        try(FileReader fileReader = new FileReader("d:/test.txt");
            FileWriter fileWriter = new FileWriter("d:/test2.txt")){
            char[] buffer = new char[1024];
            int len = -1;
            while ((len = fileReader.read(buffer)) != -1){
                fileWriter.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2)带缓冲区

 //带缓冲区 
    private static void copyFile2(){
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader("d:/test.txt"));
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("d:/test2.txt"))){
            char[] buffer = new char[1024];
            int len = -1;
            while ((len = bufferedReader.read(buffer)) != -1){
                bufferedWriter.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

3)字符流中有特殊的用法:按行读取

   private static void copyFile3(){
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader("d:/test.txt"));
            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("d:/test2.txt"))) {
                String line = "";//读一行,读到换行符为止 
                while ((line = bufferedReader.readLine()) != null){
                    bufferedWriter.write(line+ "\n");
                }
            } catch (IOException e) {
            e.printStackTrace();
        }
    }