【JAVA入门】Day38 - IO流

【JAVA入门】Day38 - IO流



        IO流是存储和读取数据的解决方案。

        File 表示系统中的文件或者文件夹的路径,File类只能对文件本身进行操作,不能读写文件里面存储的数据,因此IO流的出现就是为了读写文件里的数据。
        IO流既可以读写文件中的数据,也可以读取网络中的数据。它可以把文件中的数据写入本地(Output),也可以把文件中的数据读入到程序中(Input)。
        要明确,都是以程序为参照物,是程序在读,程序在写。

一、IO流的分类

        IO流按照流的方向分类,可以分为两种:
在这里插入图片描述

        IO流按照操作文件类型分类,可以分为两种:
在这里插入图片描述
        注意,纯文本文件指的是:Windows 自带的记事本打开能读懂的文件,比如:txt、md、xml、lrc 等,它们可以用字符流的方式处理。而 docx、xls 等文件不是纯文本文件,只能用字节流的方式处理。

二、IO流的体系结构

在这里插入图片描述
        IO流的体系分为字节流和字符流,它们都有自己的输入和输出流,但是这些输入输出流都是抽象类,不能直接创建对象,我们还要分别看它们的子类。
        以字节流为例:
在这里插入图片描述

三、FileOutputStream

        FileOutputStream 是操作本地文件的字节输出流,可以把程序中的数据写到本地文件中。
书写步骤:
① 创建字节输出流对象。
② 写数据。
③ 释放资源。
        下面的代码就是把一个字符’a’输出到本地文件 a.txt 中的过程。

package IOByteStream;

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

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
        /*
            演示:字节输出流FileOutputStream
            需求:写出一段文字到本地文件中(写出)
            实现步骤:创建对象、写出数据、释放资源
         */

        //1.创建对象
        FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\a.txt");

        //2.写出数据
        fos.write(97);

        //3.释放资源
        fos.close();
    }
}

        在输出到文件的过程中存在诸多细节:
1.创建字节输出流对象时,参数可以是字符串表示的路径,也可以是 File 对象。
2.如果参数中的文件不存在会创建一个新的文件,但是一定要保证这个文件的父级路径是存在的。
3.如果文件已存在,会先清空这个文件,再写入。
4.write 方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符,97 --> a。
5.每次使用完流之后,都要释放资源。

3.1 FileOutputStream 写数据的3种方式

        FileOutputStream 写入数据的方式一共有3种,他们是:
在这里插入图片描述
        以下演示三种写出数据方式。

package IOByteStream;

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

public class ByteStreamDemo2 {
    public static void main(String[] args) throws IOException {
        /*
            写入数据
         */

        //1.创建一个输出流
        FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\a.txt");

        //2.写出数据
        //fos.write(97);  //a
        //fos.write(98);  //b

        //一次写多个-->byte数组
        //byte[] bytes = {97,98,99,100,101};
        //fos.write(bytes);

        //一次写一个字节数组的一部分数据   void write(byte[] b, int off, int len)
        //参数一:数组    参数二:起始索引    参数三:字节个数
        byte[] bytes = {97,98,99,100,101};
        fos.write(bytes,1,2);   //b c

        //3.释放资源
        fos.close();
    }
}

3.2 FileOutputStream 写数据的两个问题

3.2.1 换行写
package IOByteStream;

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

public class ByteStreamDemo3 {
    public static void main(String[] args) throws IOException {
        /*
            换行写
            Windows换行是 \r\n
            Java中可以写 \r \n 任何一个作为换行符,在底层可以自动补全

            续写
         */

        //1.创建对象
        FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\a.txt");

        //2.写出数据
        //kankelaoyezuishuai
        String str1 = "kankelaoyezuishuai";
        byte[] bytes1 = str1.getBytes();
        fos.write(bytes1);

        String br = "\r\n";
        byte[] bytes2 = br.getBytes();
        fos.write(bytes2);

        String str2 = "666";
        byte[] bytes3 = str2.getBytes();
        fos.write(bytes3);


        //3.释放资源
        fos.close();

    }
}
3.2.2 续写

        我们在创建 FileOutputStream 对象时,其实有第二个参数——“续写开关”。

package IOByteStream;

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

public class ByteStreamDemo4 {
    public static void main(String[] args) throws IOException {
        /*
            续写开关 boolean append
            在创建对象时,这个变量如果是true,文件就不会清空,可以续写;如果是false,文件就会被覆盖
         */

        //1.创建对象
        FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\a.txt",true);

        //2.写出数据
        //kankelaoyezuishuai
        String str1 = "kankelaoyezuishuai";
        byte[] bytes1 = str1.getBytes();
        fos.write(bytes1);
        
        //3.释放资源
        fos.close();
    }
}

四、FileInputStream

        FileInputStream 字节输入流可以操作本地文件,把里面的数据读取到程序中来。
书写步骤:
        ① 创建字节输入流对象。
        ② 读数据。
        ③ 释放资源。

package IOByteStream;

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

public class ByteStreamDemo5 {
    public static void main(String[] args) throws IOException {
        /*
            字节流去读
         */

        //1.创建对象
        FileInputStream fis = new FileInputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\a.txt");

        //2.读数据
        int b1 = fis.read();
        System.out.println(b1);                 //读取一个数据,得到的是97,是字符'a'的ASCII码值
        System.out.println((char)b1);           //强转得到'a'

        //read() 方法是负责读取文件里的数据的,而且是一个一个地读,如果最后读不到了(文件尾),会返回-1

        //3.释放资源
        fis.close();
    }
}

        在输入到程序的过程中,存在诸多细节:
1.如果文件不存在,就直接报错。
2.read 方法一次读一个字节,读出来的是数据在ASCII上对应的数字。
3.每调用一次 read 方法,“指针”往后移动一位。如果读取多次,读到文件末尾了,read 方法返回 -1。
4.每次使用完流必须要释放资源。

4.1 FileInputStream 循环读取

        由于 read 方法每次只能读取一个字节,所以我们要学习循环读取,这样才能读完一个文件中所有的内容。
        定义一个变量 b ,用来存储读到的每一个字符,然后用一个 while 循环打印每一个字符,循环条件用 read() 方法赋值给 b,每进行一次这个赋值,读取字符的“指针”就前进一步,循环直到 read() 方法返回 -1,此时意味着读到了文件尾,循环结束。

package IOByteStream;

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

public class ByteStreamDemo6 {
    public static void main(String[] args) throws IOException {
        /*
            字节输入流循环读取
         */

        //1.创建对象
        FileInputStream fis = new FileInputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\a.txt");

        //2.循环读取
        int b;
        while((b = fis.read()) != -1) {
            System.out.print((char)b);
        }

        //3.释放资源
        fis.close();
    }
}

五、文件拷贝

        如何拷贝一个文件?参考下面的代码。

package IOByteStream;

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

public class ByteStreamDemo7 {
    public static void main(String[] args) throws IOException {
        /*
            文件拷贝
            把D:\IdeaProjects\HelloWord\src\Files\beCopied拷贝到当前模块下

        */

        //1.创建对象
        FileInputStream fis = new FileInputStream("D:\\IdeaProjects\\HelloWord\\src\\Files\\beCopied");
        FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\Copy.txt");

        //2.拷贝
        //核心思想:边读边写
        int b;
        while((b = fis.read()) != -1) {
            fos.write(b);
        }

        //3.释放资源
        //规则:先开的流最后再关闭
        fos.close();
        fis.close();

    }
}

        文件拷贝存在一个很严肃的问题,如果拷贝的文件过大,速度会不会有影响?会的,速度非常慢。因为 FileInputStream 在拷贝时,一次只读写一个字节,有多少个字节,就要循环多少次。
        如果想要提高拷贝的速度,就需要一次读取多个字节,它也有相关的方法,看下面的表格:
在这里插入图片描述
        read 方法的重载可以实现一次读取多个字节,我们在拷贝时,可以创建一个较大的数组(最好是1024的整数倍),比如:1024 * 1024 * 5,50MB的数组来进行拷贝。
        在下面的例子中,我们读取了一个文件中的数据,我们使用的是一个2字节的数组,每次读取2个字节的数据,但在最后文件尾,出现了问题,我们在读取新数据时,读取到的字符会替换原数组中的数据,但是在读取文件尾的"e"时,只替换了数组的0索引,而1索引的数据没有被替换,这就导致倒数第二次读取到"cd",最后一次还是读取到"ed",这是错误的。

package IOByteStream;

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

public class ByteStreamDemo8 {
    public static void main(String[] args) throws IOException {
        /*
            public int read(byte[] buffer)      一次读取一个字节数组的数据

         */

        //1.创建对象
        FileInputStream fis = new FileInputStream("D:\\IdeaProjects\\HelloWord\\src\\Files\\beCopied");

        //2.读取数据
        byte[] bytes = new byte[2];
        //一次读取多个字节数据,具体读多少,跟数组长度有关
        //返回值:本次读取到了多少个字节数据

        //文件里是abcde
        int len1 = fis.read(bytes);
        System.out.println(len1);

        String str1 = new String(bytes);        //len 2
        System.out.println(str1);               //数组:   a   b

        int len2 = fis.read(bytes);
        System.out.println(len2);

        String str2= new String(bytes);         //len 2
        System.out.println(str2);               //数组:   c   d

        int len3 = fis.read(bytes);
        System.out.println(len3);

        String str3= new String(bytes);         //len 1
        System.out.println(str3);               //数组:   e   d

        int len4 = fis.read(bytes);
        System.out.println(len4);

        String str4= new String(bytes);         //len -1
        System.out.println(str4);               //数组:   e   d

        //3.释放资源
        fis.close();
    }
}

        因此我们再读取时,要指定 String() 中读取到的字符串长度,使用new String(byte[] b, int n, int m) 构造方法,保证每次生成的字符串和读取到的字节个数保持一致。如下面的代码:

package IOByteStream;

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

public class ByteStreamDemo8 {
    public static void main(String[] args) throws IOException {
        /*
            public int read(byte[] buffer)      一次读取一个字节数组的数据

         */

        //1.创建对象
        FileInputStream fis = new FileInputStream("D:\\IdeaProjects\\HelloWord\\src\\Files\\beCopied");

        //2.读取数据
        byte[] bytes = new byte[2];
        //一次读取多个字节数据,具体读多少,跟数组长度有关
        //返回值:本次读取到了多少个字节数据

        //文件里是abcde
        int len1 = fis.read(bytes);
        System.out.println(len1);

        String str1 = new String(bytes,0,len1);        //len 2
        System.out.println(str1);                            //数组:   a   b      生成的字符串:a    b

        int len2 = fis.read(bytes);
        System.out.println(len2);

        String str2= new String(bytes, 0,len2);         //len 2
        System.out.println(str2);                             //数组:   c   d     生成的字符串:c    d

        int len3 = fis.read(bytes);
        System.out.println(len3);

        String str3= new String(bytes, 0,len3);         //len 1
        System.out.println(str3);                              //数组:   e   d    生成的字符串:e

        int len4 = fis.read(bytes);                            //len -1
        System.out.println(len4);                              //文件尾

        //3.释放资源
        fis.close();
    }
}

        通过一次读取 byte[ ] 中多个字节的数据,我们可以改写文件拷贝,让它变得更简洁:

package IOByteStream;

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

public class ByteStreamDemo9 {
    public static void main(String[] args) throws IOException {
        /*
            "D:\\IdeaProjects\\HelloWord\\src\\Files\\beCopied"拷贝到"D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\Copy.txt"
         */

        long l1 = System.currentTimeMillis();

        //1.创建IO流
        FileInputStream fis = new FileInputStream("D:\\IdeaProjects\\HelloWord\\src\\Files\\beCopied");
        FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\Copy.txt");

        //2.一次读取多个字节的数据
        int len;
        byte[] bytes = new byte[1024 * 1024 * 5];
        while((len = fis.read(bytes)) != -1) {
            fos.write(bytes,0, len);            //将bytes[] 数组中 0索引开始 len 长度的数据写入文件
        }

        //3.关闭IO流
        fis.close();
        fos.close();

        long l2 = System.currentTimeMillis();

        System.out.println(l2 - l1);            //用了2毫秒
    }
}

六、捕获IO流异常

        之前在写IO流文件操作时,我们都是用 throws 抛出异常,如果我们想用 try…catch 结构来捕获异常,应该怎么办?

package IOByteStream;

import java.io.FileOutputStream;
import java.io.IOException;

public class ByteStreamDemo10 {
    public static void main(String[] args) {
        try {
            FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\a.txt");
            fos.write(97);          //如果在这里抛出异常,会导致JVM跳过fos.close()代码
            fos.close();

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

        如果还按之前的结构写,在 write() 方法处如果发生异常抛出,就会导致后续的 fos.close() 方法无法执行,导致流无法释放,因此我们需要完善 try…catch 结构,使用它的完整结构 try…catch…finally。

package IOByteStream;

import java.io.FileOutputStream;
import java.io.IOException;

public class ByteStreamDemo10 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\a.txt");
            fos.write(97);          
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            fos.close();
        }
    }
}

        finally 的特点是:里面的代码一定会被执行,无论是否抛出并捕获异常,最终代码都会执行 { } 里的部分,除非虚拟机被停止(比如 exit() 方法)。
        还有一个细节要注意,FileInputStream 或者 FileOutputStream 流对象要在 try…catch 体系外声明并初始化为 null,因为如果写在 try { } 当中声明,生成的是局部变量,它是无法在 finally { } 结构中使用的,因此先声明,再在 try { } 中接收对象。
        虽然在套入 try…catch…finally 结构之后,fos.close() 语句被放到 finally { } 中一定会执行,但是我们要意识到,fos.close() 语句也是可能发生异常的,这里的异常可能有两种情况:

  • fos = new FileInputStream() 语句中的路径不存在,此时输出通道建立失败,fos 还是 null,如此用 fos 调用 close() 方法就会发生空指针异常。
  • fos.close() 方法本身也可能抛出 IOException 异常。

        因此我们还要对 fos.close() 语句进行单独判断:使用 if 判断 fos 是否为空,如果 fos 还是 null,代表通道建立失败,此时不必释放资源,也就不用调用 close() 方法;如果 fos 非空,可以调用 close() 方法,此时用另一个 try…catch 结构包裹 fos.close() 语句。

package IOByteStream;

import java.io.FileOutputStream;
import java.io.IOException;

public class ByteStreamDemo10 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = null;

        try {
            fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\a.txt");
            fos.write(97);          //如果在这里抛出异常
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos != null) {
                try {
                    fos.close();
                } catch(IOException e) {
                    e.printStackTrace();
                }
            }             
        }
    }
}

        经过上面的改进,我们可以写出完整的包含异常抛出的文件拷贝代码:

package IOByteStream;

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

public class ByteStreamDemo11 {
    public static void main(String[] args) {
        /*
            文件拷贝
         */

        //1.创建IO流
        FileInputStream fis = null;
        FileOutputStream fos = null;

        try {
            fis = new FileInputStream("D:\\IdeaProjects\\HelloWord\\src\\Files\\beCopied");
            fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\Copy.txt");

            int len;
            byte[] bytes = new byte[1024 * 1024 * 5];
            while((len = fis.read(bytes)) != -1) {
                fos.write(bytes,0,  len);
            }
        } catch(IOException e) {
            e.printStackTrace();
        } finally {

           if(fos != null) {
               try {
                   fos.close();
               } catch(IOException e) {
                   e.printStackTrace();
               }
           }

           if(fis != null) {
               try {
                   fis.close();
               } catch(IOException e) {
                   e.printStackTrace();
               }
           }
        }
    }
}

        思考上面的完整代码,代码是否过于繁琐?为此,Java 自己也意识到了,于是提供了新的改进方案。
在这里插入图片描述
        通过上面的写法,可以让创建的流对象在使用完以后自动释放资源,但要注意,不是所有的流都可以写在“创建流对象”的位置,只有实现了 AutoCloseable 接口的流对象才可以写在这里(比如我们的 FileOutputStream 和 FileInputStream)。当 try…catch 体系执行完毕之后,系统会自动释放资源。
        JDK7提供的书写方案如下:

package IOByteStream;

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

public class ByteStreamDemo12 {
    public static void main(String[] args) {
        /*
            fis = new FileInputStream("D:\\IdeaProjects\\HelloWord\\src\\Files\\beCopied");
            fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\Copy.txt");
            JDK7 写法
         */

        try(FileInputStream fis = new FileInputStream("D:\\IdeaProjects\\HelloWord\\src\\Files\\beCopied");
        FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\Copy.txt")){
            //拷贝代码
            int len;
            byte[] bytes = new byte[1024 * 1024 * 5];
            while((len = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, len);
            }
        } catch(IOException e) {
            e.printStackTrace();
        }

        //无须在finally写释放资源代码,资源会自动释放
    }
}

        JDK9提供的书写方案如下:

package IOByteStream;

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

public class ByteStreamDemo13 {
    public static void main(String[] args) throws FileNotFoundException {
        /*
            fis = new FileInputStream("D:\\IdeaProjects\\HelloWord\\src\\Files\\beCopied");
            fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\Copy.txt");
            JDK9 写法 IO流中捕获异常
         */
            FileInputStream fis = new FileInputStream("D:\\IdeaProjects\\HelloWord\\src\\Files\\beCopied");
            FileOutputStream fos = new FileOutputStream("D:\\IdeaProjects\\HelloWord\\src\\IOByteStream\\Copy.txt");
            try(fis;fos){
                //拷贝代码
                int len;
                byte[] bytes = new byte[1024 * 1024 * 5];
                while((len = fis.read(bytes)) != -1) {
                    fos.write(bytes, 0, len);
                }
            } catch(IOException e) {
                e.printStackTrace();
            }

            //无须在finally写释放资源代码,资源会自动释放
    }
}
  • 19
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值