梁恒嘉的技术专栏 全栈之路

Java中的IO流

2022-06-25
NanKe

记录Java中IO流相关知识,包括IO流基本概念,IO流中的相关类,FileInputStream类,缓冲流(处理流),文件分割与合并,FileReader与FileWriter,字符打印流与对象输入,对象输出流,包含文件的切割与合并完整代码

一、IO流

IO流(输入流/输出流)分为字节流与字符流。

输入流与输出流是相对于程序而言。输入流是将本地或者其他终端文件读入到程序中。输出流是指将程序中的数据写入到本地或者其他终端。

根据操作方式将流分为字节流与字符流。

字节流:按照字节的方式操作数据源。

字符流:按照字符方式操作数据源。

一般而言以stream结尾的类都是字节流。Readre结尾或者Writter结尾是字符流。

根据流里面的内容可以将流分为节点流与处理流。

节点流:流里面的内容是数据源。

处理流:流里面内容是节点流。

二、IO相关类

1.FileInputStream类

当我们对文件进行操作的时候,我们会使用字节流去处理。我们使用字节流处理的时候,在读入数据到程序时,会有两个read()方法,两个read方法返回值都是int,但表示的意义不同。并且读入的方式有区别。当我们对文件对象进行操作的时候,一定要记得关闭流对象。 代码如下(示例):

    private static void save1(String src, String des) {
        FileInputStream in = null;
        FileOutputStream out = null;
        //根据路径创建文件对象
        File file = new File(src);
        try {
            //创建输入流对象,将文件读取到程序
            in = new FileInputStream(file);

            //创建输出流对象,将文件数据从程序中写入到指定路径下的文件
            out = new FileOutputStream(des);

            //此时的value表示从文件中读取的一个字节内容,当读取到结尾的的时候,read()将会返回-1
            int value = 0;
            while ((value = in.read()) != -1) {
                out.write(value);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
private static void save2(String src, String des) {
        File file = new File(src);
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            in = new FileInputStream(file);
            out = new FileOutputStream(des);
            byte[] bytes = new byte[10];
            //此时的size表示的是我们的byte数组中实际存入数据的长度
            int size = 0;
            while ((size = in.read(bytes)) != -1) {
                out.write(bytes, 0, size);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

2.缓冲流(处理流)

使用处理流提高效率。

代码如下:

private static void save3(String src, String des) {
        //使用处理流提高效率
        BufferedInputStream bin = null;
        BufferedOutputStream bout = null;
        try {
            InputStream in = new FileInputStream(src);
            OutputStream out = new FileOutputStream(des);
            //在输入缓冲流底层存在一个缓冲数组,大小是8192B(8KB),在构造方法里面我们也可以传入自定义的大小
            //当我们使用缓冲流的时候,使用的byte数组大小小于8192时,我们每一次写入数据只会发生在填充满8192缓冲数组之后。
            //当我们使用缓冲流的时候,使用的byte数据大小大于8192时,缓冲流里面的缓冲数组失效。
            bin = new BufferedInputStream(in);
            bout = new BufferedOutputStream(out);
            int size = 0;
            byte[] bytes = new byte[1024];
            while ((size = bin.read(bytes)) != -1) {
                bout.write(bytes,0,size);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (bin!=null){
                try {
                    bin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                bout.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (bout!=null){
                try {
                    bout.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.文件分割与合并

写一个方法,将文件分割为每份1MB大小的若干份,存储在一个temp的文件夹中(每份文件名自己定义), 如:1.temp 2.temp

然后再写一个方法,将这若干份合并为一个文件。

  输入 输出
分割 一个文件 多个文件
合成 多个文件 一个文件

代码如下:

private static void cut(String path) throws IOException {
        //传递的path参数是一个盘符的地址,是我们将要分割文件的所在路径。
        // 如:F:/y2mate.com - ONCE UPON A TIME IN SHANGHAI OST_720p.mp4
        //在F盘存在一个文件,将这个文件分割(按照大小为1M)分割成多个临时文件保存到F:/temp文件下
        File file = new File(path);
        FileInputStream in = new FileInputStream(file);

        //创建文件夹temp,去存储我们分割的临时文件。
        File temp = new File("F:/temp");
        if (!temp.exists()) {
            temp.mkdir();
        }

        int size = 0;
        int name = 1;
        byte[] bytes = new byte[1024 * 1024];
        while ((size = in.read(bytes)) != -1) {
            FileOutputStream out = new FileOutputStream("F:/temp/" + (name++) + ".temp");
            out.write(bytes, 0, size);
            out.close();
        }
        in.close();
    }

    private static void mix(String path) throws IOException {
        //传入的参数是我们需要合并的临时文件所在的文件路径
        //将盘符文件夹下面的所有文件合并成一个完整的文件。如:F:/temp
        File file = new File(path);
        File[] files = file.listFiles();

        //在这个传递的参数是我们需要分割前文件的文件路径。
        FileOutputStream out = new FileOutputStream(path + "/源文件.mp4");
        int name = 1;
        for (int i = 0; i < files.length - 1; i++) {
            int size = 0;
            byte[] bytes = new byte[1024];
            //在这个地方记载一个问题(如果不使用文件名进行拼接,只是使用files数组传递,视屏文件将会出现播放问题)
            FileInputStream in = new FileInputStream("F:/temp/" + (name++) + ".temp");

            //下面这一步是不正确的,因为files数组返回的文件不是我们预期样子
            //FileInputStream in = new FileInputStream(files[i]);
            //预期的样子(数组下标索引为0对应的是1.temp,数组索引下标为1对应的是2.temp)
            //实际样子(数组下标索引是递增的,但是每一个数组的元素名称不一定递增)
            while ((size = in.read(bytes)) != -1) {
                out.write(bytes, 0, size);
            }
            in.close();
        }
        out.close();
    }

特别注意:File里面的listFiles()方法返回的是一个文件数组,这个文件数组里的文件不是按照我们预期的样子排列。数组的下标索引与文件的名称不是递增顺序排列。

files文件数组里面数组下标索引对应的文件如图所示:

2ed65556d2024f778f515fc5304764af

temp文件夹文件按照数字递增排序,这个顺序与我们的分割顺序是一致的。合并时必须按照这个顺序。

3ac368aaa78e4b06b6303ba1ea009e05

导致的问题:文件合并有问题。如:我合并的视频文件,播放前六秒正常,之后的图像不动,音频静音,只有视频进度条走动。

解决方案:我们在合并文件的时候,必须按照文件的序号递增一个一个读取到程序,最后按照分割的先后顺序写入到文件中。

使用手动拼接文件名的方式,保证顺序一致。

4.FileReader与FileWriter

FileRead与FileWriter是字符流,通过InputStreamReader与InputStreamWriter处理流将内容转换成字符。

代码如下:

private static void read() throws IOException {
        File file = new File("F:/demo.txt");
        FileReader reader = new FileReader(file);
        FileWriter writer = new FileWriter("F:/demo1.txt");
        BufferedReader bufferedReader = new BufferedReader(reader);
        BufferedWriter bufferedWriter = new BufferedWriter(writer);

        int size = 0;
        char[] chars = new char[1024];
        while ((size = bufferedReader.read(chars)) != -1) {
            bufferedWriter.write(chars, 0, size);
        }
        bufferedReader.close();
        bufferedWriter.flush();
        bufferedWriter.close();
    }
    private static void readerLine() throws IOException {
        File file = new File("F:/demo.txt");
        FileReader reader = new FileReader(file);
        BufferedReader bufferedReader = new BufferedReader(reader);
        FileWriter writer = new FileWriter("F:/demo1.txt");
        BufferedWriter bufferedWriter = new BufferedWriter(writer);

        String readLine;
        while ((readLine = bufferedReader.readLine()) != null) {
            bufferedWriter.write(readLine);
        }
        bufferedReader.close();
        bufferedWriter.flush();
        bufferedWriter.close();
    }

5.字符打印流与对象输入,对象输出流

打印流分为字节打印流与字符打印流。从输入输出方面只存在输出打印流,不存在输入打印流。

当我们需要存储对象信息的时候(这个过程称为对象序列化),我们需要使用对象输出流;当我们需要把存储的对象信息读取到程序里面的时候(这个过程称为对反象序列化),我们需要使用对象输入流。

代码如下:


    /*  打印流:分为字节打印流与字符打印流
        按照输入与输出讲,打印流只存在输出流打印流,不存在输入打印流
    *   演示字符打印流的操作
    * */
    private static void printWriter() throws FileNotFoundException {

        File file = new File("F:/demo.html");
        PrintWriter stream = new PrintWriter(file);
        stream.print("<h1>Hello World!</h1>");
        stream.close();
    }

    /**
     * 对象输出流 与 对象输入流(两者属于处理流)
     * 我们把对象信息打印到文件中称为对象的序列化,也就是对象输出流
     * 把对象信息从文件中读取出来,称为对象的反序列化,也就是对象输入流
     * 对象序列化时存在一个UID,这是类的唯一编号
     * 如果我们需要将自定义的类进行序列化操作,我们应该实现Serializable,并显示将UID声明出来。
     * 当我们不显示声明这个UID时,系统会默认生成一个UID
     * 但是一旦我们的类发生改变,这个之前声明的UID将会失效(类发生改变后,我们之前序列化的对象,将找不到对应的类信息)
     * 类的UID标识出类
     */
    private static void objectStream() throws IOException, ClassNotFoundException {

        FileOutputStream out = new FileOutputStream("F:/object.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
        Student simon = new Student("Simon", 22);
        objectOutputStream.writeObject(simon);
        objectOutputStream.close();

        File file = new File("F:/object.txt");
        InputStream inputStream = new FileInputStream(file);
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        Student stu = (Student)objectInputStream.readObject();
        System.out.println(stu);
        objectInputStream.close();
    }

总结

IO流这块,首先要明确输入流与输出流的概念(把计算机程序作为参照物,将数据信息读取到程序中,我们称之为输入;我们将程序中的数据信息写入到文件或者其他媒介中,我们称之为输出)。

其次根据我们读取的数据内容分为字节流与字符流。

根据流里面的信息我们将流分为节点流(流里面的信息是数据内容)与处理流(流里面的信息是节点流)。

最后我们学习打印流与对象流。

打印流是将我们程序中的数据信息打印出来,可以使用文件存储打印的信息。

对象流是将程序中的对象信息存储起来(对象序列化),或者将存储的对象信息读取出来(对象反序列化)。

温故而知新,是一种理想状态。更多的是对已学习知识加深记忆与理解。多次的学习是提高自己编程技巧,查漏补缺的最佳方法。


下一篇 Java网络编程

 

Content