查看原文
其他

IO流概述

树莓蛋黄派 Java规途 2023-07-04
  • IO流概述(Input Output Stream)

  • IO流的分类

    • 1.按照流的方向来分

    • 2. 按照处理的数据单元分类

    • 3.按处理对象不同来分类

  • IO流体系结构

    • 字节流

    • Reader和Writer

  • File类的使用

    • 示例1:获取文件或者文件夹的属性

    • 示例2:实现对文件或文件夹的创建或者删除操作

  • 文件字节流InputStream和OutputStream

    • 复制文件(中转站是一个字节)

    • 中转站缺点

    • 复制文件(中转站是一个字节数组)

    • 进行异常处理

    • JDK7异常处理新特征

    • JDK9出现的新的异常处理机制

  • 文件字符流FileReader和FileWriter

    • 复制文件(中转站是一个字符)

  • 缓冲字节流BufferedInputStream和BufferedOutputStream

    • 复制文件(使用缓冲字节流提高效率)

  • 缓冲字符流BufferedReader和BufferedWriter

    • 缓冲字符流复制文件

    • 总结1:BufferedReader和BufferedWriter的优点

    • 总结2:readLine()底层原理

    • 总结3:不同操作系统下的换行符是不同的。

  • 数据流 DataInputStream和DataOutputStream

    • 使用数据流读写文件

  • 对象流ObjectInputStream和ObjectOutputStream

  • 往期文章回顾

IO体系

IO流概述(Input Output Stream)

在Java程序中,数据的输入与输出都是通过“流”的形式来处理。Java提供了各种各样的“流"类,用以获取不同种类的数据。程序中通过标准的方法输出或者输入数据。

Java的流类型一般位于java.io包中。

「数据源」:提供原始数据的原始媒介,常见的有IO设备、数据库、文件、其他程序、内存、网络连接等

「流」:是一个动态抽象的概念,是一串连续动态的集合。

数据源就像是水箱,流就像是水管中的水流,程序就是我们最终用户。

IO流的分类

1.按照流的方向来分

  • 「输入流」:数据流向是数据源到程序(InputStream、Reader结尾的流)
  • 「输出流」:数据流向是是程序到目的地(OutputStream、Writer结尾的流)

输入输出流的划分是相对于程序而言,而不是数据源

2. 按照处理的数据单元分类

  • 「字节流」:以字节为单位获取数据,命名上以「Stream」结尾的一般是字节流,顶级类:「InputStream」「OutputStream」
  • 「字符流」:以字符为单位获取数据,命名上以「Reader」或者「Writer」结尾的一般是字符流。顶级类:「Reader」「Writer」

3.按处理对象不同来分类

  • 「节点流」:可以直接从数据源或者目的地读写数据,如「FileInputStream」或者「FileReader」
  • 「处理流」:不直接连接到数据源或者目的地,是“处理流的流”。通过对其他流的处理提高程序的性能。如「BufferedInputStream」「BufferedReader」,处理流也叫做包装流

节点流处于IO操作的第一线,所有操作必须通过他们进行。

处理流可以通过对节点流进行包装,提高程序的性能和灵活性

节点流与处理流的关系

IO流体系结构

字节流

  • InputStream和OutputStream是Java中最基本的两个字节输入输出类,其他所有的字节输入输出流类都要继承这两个基类。
  • 这两个类都是抽象类,不能够创建实例,只能使用他们的子类。
  • 「FilterInputStream」「FilterOutputStream」是所有「包装流」的父类。

输入字节流

输出字节流

Reader和Writer

  • Java中两个最基本的字符输入输出类
  • 其他所有的字符输入输出流类都继承自这两个基类。
  • 这两个类都是抽象类,不能够创建他们的子类,只能使用他们的子类。

Reader体系结构

Writer体系结构

File类的使用

File类用来代表「文件」「文件夹」

主要作用有两个:

  1. 获取文件或者文件夹的属性
  2. 实现对文件或者文件夹的创建和删除。

文件夹:file folder,目录:directory

示例1:获取文件或者文件夹的属性

public class TestFileGetField {
    public static void main(String[] args) {
        /*
        * 让File类的对象指向一个文件*/

        //利用File类来获取/home/JavaIO示例目录下的txt文件  /data/home/lambda/JavaIo文件示例/ReadMe.txt
        //绝对路径,根路径
        File file=new File("/data/home/lambda/JavaIo文件示例/ReadMe.txt");

        //相对路径,用的比较少
        File file1=new File("ReadMe.txt");
        //文件名
        System.out.println(file.getName());
        //长度
        System.out.println(file.length());
        //是否存在
        System.out.println(file.exists());
        //获取绝对路径的方法,如果采用相对路径的方式创建file,它获取的是该项目所在路径的文件(与该文件同名)
        // 。即/home/lambda/idea软件/ReadMe.txt
        System.out.println(file.getAbsolutePath());

        System.out.println("=================================");

        //查看该文件是否可以读
        System.out.println(file.canRead());
        //查看该文件是否可以写
        System.out.println(file.canWrite());
        //查看该文件是否可以执行
        System.out.println(file.canExecute());

        System.out.println("=================================");
        //查看file是文件吗?
        System.out.println(file.isFile());
        //查看file是否是文件夹?
        System.out.println(file.isDirectory());

        System.out.println("=================================");
        /*
        * 让File类的对象指向一个文件夹*/

        File file2=new File("/data/home/lambada/JavaIo文件示例/JAVAIOExercises" );
        //查看file是文件吗?
        System.out.println(file2.isFile());
        //查看file是否是文件夹?
        System.out.println(file2.isDirectory());
      /*  //查看该目录下所有的文件
        var files = file2.listFiles();
        System.out.println(files.length());*/

    }
}


示例2:实现对文件或文件夹的创建或者删除操作

public class TestFileCreateAndDelete {
    public static void main(String[] args) throws IOException {
        /**
         * 创建或者删除文件
         * */

        //创建file对象,指向一个文件
        File file=new File("/data/home/lambda/JavaIo文件示例/ReadMe.txt");
        //如果文件存在就删除,不存在就创建
        if (file.exists()){
            file.delete();
        }else{
            file.createNewFile();
        }

        /**
         * 创建或者删除一个目录*/

        //创建一个file对象,指向一个文件夹  /data/home/lambda/JavaIo文件示例
        File file1=new File(" /data/home/lambda/JavaIo文件示例/JavaIOexercises");
        //如果文件夹存在就删除,不存在就创建
        if (file1.exists()){
            file1.delete();
        }else{
            //如果上级文件夹不存在,那就创建,此处是存在的
            file1.mkdir();
            //新建多级目录
            file1.mkdirs();
        }

    }
}

文件流

文件字节流InputStream和OutputStream

  • InputStream和OutputStream都是字节流,是「节点流」,数据源和目的地都是文件。
  • 复制文件需要分别创建一个输入流和输出流完成文件的读写。
  • 需要创建一个中转站,借助循环和中转站完成文件复制
  • 流使用完毕之后一定要关闭,这和垃圾回收没有关系。

文件字节流操作示意图

复制文件(中转站是一个字节)

public class TestCopyOneWithFileStream {
    public static void main(String[] args) throws IOException {
        //创建输入流和输出流 /data/home/lambda/JavaIo文件示例
        File file1=new File("/data/home/lambda/JavaIo文件示例/ReadMe.txt");
        File file2=new File("/data/home/lambda/JavaIo文件示例/ReadMe2.txt");
        //此时表示读取file1内的信息,输入流(相对程序而言)
        InputStream fis=new FileInputStream(file1);

        OutputStream fos=new FileOutputStream(file2);

        //使用输入流和输出流,完成文件复制
      /**  定义一个中转站,中转站是一个字节,较小。
            int n;
        读一个字节。返回值为int
        n=fis.read();

        写入一个字节
        fos.write(n);
       此处需要不停地进行读写操作。
*/

      //定义一个中转站,中转站是一个字节,较小。
      int n;
      // 读一个字节。返回值为int
      n=fis.read();
      //此处进行判断,如果n不为-1,则继续写入并再次读取字节,若n为-1则表示该该文件已被完全读取,不需要在写入了。
      while (n!=-1){
          fos.write(n);
          n=fis.read();
      }

        //关闭输入和输出流
        fis.close();
        fos.close();
    }
}

中转站缺点

  • 中转站太小,速度慢,效率低,复制更大文件时效果更明显
  • 可以将中转站由一个字节转变为一个字节数组,减少对硬盘的读写次数。

复制文件(中转站是一个字节数组)

public class TestCopyTwoWithFileStream {
    public static void main(String[] args) throws IOException {

        File file1=new File("/data/home/lambda/JavaIo文件示例/ReadMe.txt");
        File file2=new File("/data/home/lambda/JavaIo文件示例/ReadMe2.txt");
        //此时表示读取file1内的信息,输入流(相对程序而言)
        InputStream fis=new FileInputStream(file1);

        OutputStream fos=new FileOutputStream(file2);
       // OutputStream fos=new FileOutputStream(file2,true);
        // 此项操作在第二次运行之后会将第二次及以后的内容都添加到file2文件之中去。

        //创建一个字节数组
        byte[] buffer=new byte[1024];
        //先读内容到一个字节处。
        //读取文件的内容放入到字节数组中,返回读取到的字节数。
        int len=fis.read(buffer);
        while (len!=-1){
            //将字节数组的内容写入文件
            // fos.write(buffer);它是读写了1024个字节,并没有指定从哪里读到哪里。
            fos.write(buffer,0,len);
            //再读内容到一个字节数组
              len=fis.read(buffer);

        }
        //关闭输入和输出流
        fis.close();
        fos.close();


    }
}

进行异常处理

public class TestCopyThreeWithFileStream {
    public static void main(String[] args)  {
        //创建输入流和输出流 /data/home/lambda/JavaIo文件示例
        File file1=new File("/data/home/lambda/JavaIo文件示例/ReadMe.txt");
        File file2=new File("/data/home/lambda/JavaIo文件示例/ReadMe2.txt");
        //此时表示读取file1内的信息,输入流(相对程序而言)
        InputStream fis= null;
        OutputStream fos=null;
        try {
            fis = new FileInputStream(file1);
            fos=new FileOutputStream(file2);

            //使用输入流和输出流,完成文件复制
            /**  定义一个中转站,中转站是一个字节,较小。
             int n;
             读一个字节。返回值为int
             n=fis.read();

             写入一个字节
             fos.write(n);
             此处需要不停地进行读写操作。
             */

            //定义一个中转站,中转站是一个字节,较小。
            int n;
            // 读一个字节。返回值为int
            n=fis.read();
            //此处进行判断,如果n不为-1,则继续写入并再次读取字节,若n为-1则表示该该文件已被完全读取,不需要在写入了。
            while (n!=-1){
                fos.write(n);
                n=fis.read();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关闭输入和输出流
            try {
                if (fis!=null){
                fis.close();}
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fos!=null){
                fos.close();
                }

            } catch (IOException e) {
                e.printStackTrace();
            }

        }



    }

}
  • 异常处理分析:创建、使用一次流要使用try-catch语句,关闭流需要分开进行异常处理/
  • Java7异常处理新特征:tyr -with-resources:不用显式进行资源关闭,只需要将资源对象放入tye后的括号中。作用范围是当前try语句,执行完毕(正常执行或者是异常情况)都会对资源进行关闭,可以省略finally语句,更加方便高效。

JDK7异常处理新特征

public class TestCopyFourWithFileStream {
    public static void main(String[] args) {
        //创建输入流和输出流 /data/home/lambda/JavaIo文件示例
        File file1=new File("/data/home/lambda/JavaIo文件示例/ReadMe.txt");
        File file2=new File("/data/home/lambda/JavaIo文件示例/ReadMe2.txt");
        //此时表示读取file1内的信息,输入流(相对程序而言)

        try ( InputStream fis=new FileInputStream(file1);
              OutputStream fos=new FileOutputStream(file2);
        ){


            //使用输入流和输出流,完成文件复制
            /**  定义一个中转站,中转站是一个字节,较小。
             int n;
             读一个字节。返回值为int
             n=fis.read();

             写入一个字节
             fos.write(n);
             此处需要不停地进行读写操作。
             */

            //定义一个中转站,中转站是一个字节,较小。
            int n;
            // 读一个字节。返回值为int
            n=fis.read();
            //此处进行判断,如果n不为-1,则继续写入并再次读取字节,若n为-1则表示该该文件已被完全读取,不需要在写入了。
            while (n!=-1){
                fos.write(n);
                n=fis.read();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

  • 关于JDK9的异常处理新变化:try之前定义好对象,try()引入创建好的对象。如果多个对象使用;分隔。如果try之前定义的对象会抛出异常就不推荐这种方式来处理异常。

JDK9出现的新的异常处理机制

public class TestCopyFiveWithFileStream {
    public static void main(String[] args) throws FileNotFoundException {
        //创建输入流和输出流 /data/home/lambda/JavaIo文件示例
        File file1=new File("/data/home/lambda/JavaIo文件示例/ReadMe.txt");
        File file2=new File("/data/home/lambda/JavaIo文件示例/ReadMe2.txt");
        //此时表示读取file1内的信息,输入流(相对程序而言)
        InputStream fis=new FileInputStream(file1);
        OutputStream fos=new FileOutputStream(file2);

        try (fis;fos){


            //使用输入流和输出流,完成文件复制
            /**  定义一个中转站,中转站是一个字节,较小。
             int n;
             读一个字节。返回值为int
             n=fis.read();

             写入一个字节
             fos.write(n);
             此处需要不停地进行读写操作。
             */

            //定义一个中转站,中转站是一个字节,较小。
            int n;
            // 读一个字节。返回值为int
            n=fis.read();
            //此处进行判断,如果n不为-1,则继续写入并再次读取字节,若n为-1则表示该该文件已被完全读取,不需要在写入了。
            while (n!=-1){
                fos.write(n);
                n=fis.read();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

文件字符流FileReader和FileWriter

  • FileReader和FileWriter是字符流,是节点流,数据源和目的地是「文件」

复制文件(中转站是一个字符)

/**
 * 使用字节流可以读写任意类型的文件,
 * 使用字符流只可以读写文本文件。
 *
 * 使用字符流处理非英文字符比较方便
 *
 * 其实只有字节流,没有字符流。*/

/**
 * @author lambda
 */

public class TestCopyFileTwoWithFileWriterAndFileReader {

    public static void main(String[] args) throws IOException {
        File file1=new File("/data/home/lambda/JavaIo文件示例/ReadMe.txt");
        File file2=new File("/data/home/lambda/JavaIo文件示例/ReadMe3.txt");
        //1.创建输入输出流  /data/home/lambda/JavaIo文件示例
        //   Reader fr=new FileReader("/data/home/lambda/JavaIo文件示例/ReadMe.txt");
        // Writer fw=new FileWriter("/data/home/lambda/JavaIo文件示例/ReadMe3.txt");
        Reader fr=new FileReader(file1);
        Writer fw=new FileWriter(file2);

        //2.使用输入流和输出流复制文件
        char[] buf=new char[1024];
        int len=fr.read(buf);
        while (len!=-1){
            fw.write(buf,0,len);
            //  System.out.println(buf);
            System.out.println(new String(buf,0,len));
            len= fr.read(buf);
        }

        //3.关闭输入输出流
        fr.close();
        fw.close();
    }
}

  • 其实本质上来说没有字符流,全部都是字节流(字符流的底层还是由字节流来支持的)
  • 并且他们全都是「节点流」:他们都是直接与数据源相连。不是处理流。

缓冲流

缓冲字节流BufferedInputStream和BufferedOutputStream

「文件缓冲字节流工作示意图」

复制文件(使用缓冲字节流提高效率)

import java.io.*;
/**使用缓冲流的好处:
* 1.提高读写速度 
* 2.关闭高层流即可,不必关闭底层流
* 3.关闭高层流其实就是关闭底层流 
*4.缓冲流可以提高查询速度的原因:
*  引入了缓冲区,大大减少了读写硬盘的次数。
*  5.如何刷新输出缓冲区? * */

/** * @author lambda */
public class TestCopBufferedStream {  
public static void main(String[] args) throws IOException {    
File file1=new File("/data/home/lambda/JavaIo文件示例/ReadMe.txt"); 
File file2=new File("/data/home/lambda/JavaIo文件示例/ReadMe3.txt"); 
//先创建两个字节节点流  
InputStream fis=new FileInputStream(file1);  
OutputStream fos=new FileOutputStream(file2); 
//再将节点流传入两个处理流中 
BufferedInputStream bis=new BufferedInputStream(fis); 
BufferedOutputStream bos=new BufferedOutputStream(fos);   
/**     
* 简写:    
* BufferedInputStream bis =new BufferedInputStream(new FileInputStream("/data/home/lambda/JavaIo文件示例/ReadMe.txt"));    
*   BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("/data/home/lambda/JavaIo文件示例/ReadMe3.txt"));   
*      
* */
     
//此时仍把中转站算作一个字节来传输,但是缓冲流可以提高效率。 
int n=bis.read();   
while (n!=-1){     
bos.write(n);       
n=bis.read();    
}      
//关闭高层流即可(关闭缓冲字节流即可) 
bis.close();   
bos.close();  
}
}
  • 如何刷新输出缓冲区?
  1. 满了就自动刷新
  2. bos.close(),先用flush,再关闭
  3. 手动刷新flush

缓冲字符流BufferedReader和BufferedWriter

之前的读写方式都是采用一个字节或者一个字节数组的形式操作,对于文本文件而言, Java提供了这两个类来实现「按行读写」

缓冲字符流复制文件

/** * 使用缓冲流的好处 * 1.可以提高效率,BufferedInputStream和BufferedOutputStream,BufferedReader * 2.可以简化操作:BufferedReader * */
import java.io.*;
/** * @author lambda */
public class TestCopyBufferedReader 
public static void main(String[] args) throws IOException {    
//创建输入流和输出流 /data/home/lambda/JavaIo文件示例/ReadMe.txt  /data/home/lambda/JavaIo文件示例/ReadMe3.txt      
BufferedReader br=new BufferedReader(new FileReader("/data/home/lambda/JavaIo文件示例/ReadMe.txt"));     
BufferedWriter bw=new BufferedWriter(new FileWriter("/data/home/lambda/JavaIo文件示例/ReadMe3.txt "));  
//使用输入输出流复制文件(按行读取)    
String s= br.readLine();      
while (s!=null){          
bw.write(s);        
//采用写入\n来实现换行    
// bw.write("\n");      
bw.newLine();      
s=br.readLine();    
}      
//关闭输入输出流   
br.close();     
bw.close();    }}

总结1:BufferedReader和BufferedWriter的优点

  1. 速度快
  2. 简化编程

总结2:readLine()底层原理

底层本质上还是一个字符一个字符地读取,append()方法放入StringBuilder(或char数组)中

遇到换行符,将StringBuilder或者char数组转换成String并返回。

总结3:不同操作系统下的换行符是不同的。

  • Unix系统中,每行结尾只有换行,即:“\n”
  • Windows系统中,每行结尾是回车+换行。即“\r\n”
  • Mac系统中每行结尾是回车符号,即:“\r”

数据流和对象流-处理流

数据流 DataInputStream和DataOutputStream

之前使用文件流和缓冲流都只能按照字节数组的方式来读取。最方便的也就是按行读取。数据流DataInputStream、DataOutputStream和对象流ObjectInputStream、 ObjectOutputStream可以很方便的「实现对各种基本数据类型和引用类型的读写。」最大的优势就是方便操作各种数据类型的方法、直接调用,简单方便。

注意:

  1. 只有字节流,没有字符流
  2. 都是处理流不是节点流
  3. 数据流只能操作基本类型和字符串,对象流还可以操作对象。
  4. 写入的是「二进制数据」,无法用记事本直接查看。
  5. 写入的数据需要用对应的输入流来读取。

使用数据流读写文件

public class TestDataStream {  
public static void main(String[] args) {   
try {       
writer();      
read();     
catch (IOException e) {    
e.printStackTrace();       
  } 
}   
/**     * 创建一个写的方法*/   
public static void writer() throws IOException {   
//创建一个缓冲写入字节处理流。里面传入写入字节节点流对象   
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("/data/home/lambda/JavaIo文件示例/CatchMe.txt"));    
//创建一个数据流来继续封装写入处理流(写入处理流封装了文件写入节点流,而处理本身也被数据流封装)     
//此时不可以写成OutputStream dos=new DataOutputStream(bos);因为父类的方法较少。     
DataOutputStream dos=new DataOutputStream(bos);     
dos.writeInt(10);    
dos.writeDouble(3.14);      
dos.writeUTF("java");    
dos.writeChar('a');    
dos.writeBoolean(true);    
dos.close();  
}   
/**     * 创建一个读的方法*/  
public static void read() throws IOException{    
BufferedInputStream bis =new BufferedInputStream(new FileInputStream("/data/home/lambda/JavaIo文件示例/CatchMe.txt"));   
DataInputStream dis=new DataInputStream(bis);    
//直接读取写入的数据,按照顺序来读取。    
System.out.println(dis.readInt());    
System.out.println(dis.readDouble()); 
System.out.println(dis.readUTF());   
System.out.println(dis.readChar());   
System.out.println(dis.readBoolean()); 
dis.close();  
}
}

对象流ObjectInputStream和ObjectOutputStream

使用对象流读写引用数据类型时,需要相应类实现「Serializable」接口,否则会 提示异常,显示没有「序列化」

public class TestObjectStream {  
public static void main(String[] args) throws IOException {  
try {       
writer();    
read();     
catch (Exception e) {       
e.printStackTrace();      
}   
}  
public static void writer() throws IOException {    
//创建一个缓冲写入字节处理流。里面传入写入字节节点流对象   
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("/data/home/lambda/JavaIo文件示例/CatchMe.txt"));
//创建一个数据流来继续封装写入处理流(写入处理流封装了文件写入节点流,而处理本身也被数据流封装)    
//此时不可以写成OutputStream dos=new ObjectOutputStream(bos);因为父类的方法较少。   
ObjectOutputStream oos=new ObjectOutputStream(bos);     
oos.writeInt(10);    
oos.writeDouble(3.14);   
oos.writeUTF("java");  
oos.writeChar('a');
oos.writeBoolean(true);   
oos.writeObject(new Date());  
oos.close();  
}   
public static void read() throws IOException, ClassNotFoundException 
BufferedInputStream bis =new BufferedInputStream(new FileInputStream("/data/home/lambda/JavaIo文件示例/CatchMe.txt"));  
ObjectInputStream ois=new ObjectInputStream(bis);    
//直接读取写入的数据,按照顺序来读取。 
System.out.println(ois.readInt());  
System.out.println(ois.readDouble());  
System.out.println(ois.readUTF());    
System.out.println(ois.readChar());
System.out.println(ois.readBoolean());   
Date date=(Date) ois.readObject();  
System.out.println(date);     
ois.close(); 
}
}

往期文章回顾

你不可不知的语言---JAVA

Java基本程序设计结构——数据类型

变量+运算=?

字符串详解

输入输出与流程

数组与大数

类初识

自定义类与时间类

方法参数与对象构造

包、注释及JAR文件

继承及其子类

Object类及其方法

泛型数组列表及包装类



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存