美文网首页Android开发经验谈Android开发Android技术知识
什么是 Java 输入输出流?流的用法大全

什么是 Java 输入输出流?流的用法大全

作者: Android架构师丨小熊 | 来源:发表于2019-08-16 21:50 被阅读7次

I/O 流

关于数据的存储,分为内部存储和外部存储。内部存储就像内存一样,是一种临时性的对数据进行储存的方式,这种方式存储的数据量不大,但是获取数据块。另一种就是持久性的存储,以文件的方式来保存数据,可以是数据库、文本文件、图片文件、视频文件等等,存储的数据量非常大。

Java提供了一种对文件进行操作的API,就是I/O流。I/O流是Java中的一个非常庞大的家族,同时这个家族也非常强大。关于流的概念,我们可以这样的理解。水流,流的是水;电流,流的是电;I/O流,流的就是与计算机相关的二进制字节码、字符码。

I/O(Input / Output)就是标准的输入和输出,加上流的话。那么就是InputStream / OutputStream。流这个家族成员有很多,下面我们来通过一个表格来看看常用的流。

(1)流的家族

整个流的家族差不多也就这些了。通过以上的表格,我们大致了解了流的家族成员,下面我们来看看关于流的分类。

(2)流的分类

  1. 输入流 、输出流的作用
    输入流,用于从 .txt文件中读取数据;输出流,向文件中写入数据

  2. 字节流 、字符流的区别
    字节流,byte来接收数据,作用于任何类型的文件。

    字符流,char来接收数据。只能作用于纯文本(.txt)文件。如果是纯文本文件,读取速度比字节流更快。

  3. 节点流 、 处理流
    节点流,直接作用于文件上,如:new FileInputStream(File file);

  4. 处理流 , 作用于流上,如:new BufferedInputStream(InputStream is),作用是加速流的读取/写入速度。

File 类

介绍了整个流家族,我们还缺一个与流息息相关的 File 类。顾名思义,这是对文件进行创建、删除操作的一个类。而流则就是对文件的读取/写入操作的类。对于文件的操作,我们直接看代码例子比较鲜明,这也没什么理论好说的。

    @Test
    public void file() throws IOException {
        // 绝对路径:d:/FileTest/test ,新建文件夹
        File dirFile = new File("d:/FileTest/test");
        if (!dirFile.exists()) {
            dirFile.mkdirs();
        }
        // 相对路径:C:\Users\x\eclipse-workspace\HelloJava\Test.txt ,新建文件
        File helloFile = new File("Test.txt");
        if (!helloFile.exists()) {
            helloFile.createNewFile();
        }
 
        // 列出 d:/FileTest/test 文件夹下的所有文件名
        File nameFile = new File("d:/FileTest/test");
        String[] fileName = nameFile.list();
        for (String name : fileName) {
            System.out.println(name);
        }
 
        //判断文件的存在性
        System.out.println(helloFile.exists());
        //获取绝对路径
        System.out.println(helloFile.getAbsolutePath());
        //获取父目录
        System.out.println(helloFile.getParent());
        // 获取最后修改文件的时间
        System.out.println(new Date(helloFile.lastModified()));
    }

注意点:例如,D:/FileTest/test ,使用 mkdir 与 mkdirs 的区别 mkdir 前提是 FileTest 存在的情况下才可以创建成功 mkdirs 不需要,若 FileTest 文件夹不存在,则一并创建。

I/O流的用法

  • FileInputStream/FileOutputStream
    这两个是节点流,可以直接作用于文件上。例如,从文本文件中读取数据打印到控制台上:

  • FileInputStream 实现读取文件

    @Test
    public void fisTest() {
        File file = new File("Hello.txt");
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            byte[] b = new byte[10];
            int length;
            while ((length = fis.read(b)) != -1) {
                String str = new String(b, 0, length);
                System.out.print(str);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

比较难理解的就是while循环,它表达的是从 Hello.txt 文件中读取byte[10]这样长度的字节,然后打印到控制台。若已经读到结尾,则没有数据可以读了,就会返回 -1

  • FileOutputStream 实现写入文件
    @Test
    public void fosTest() {
        // 指定文件路径,若不存在,则会自动创建
        File file = new File("Hello.txt");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            byte[] b = new String("hello Java 2").getBytes();
            fos.write(b, 0, b.length);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

FileInputStream/FileOutputStream 实现文件拷贝

    @Test
    public void copyTest() {
        long start = System.currentTimeMillis();
 
        File srcFile = new File("mv.mp4");
        File destFile = new File("mv3.mp4");
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            byte[] b = new byte[4 * 1024];
            int len;
            while ((len = fis.read(b)) != -1) {
                fos.write(b, 0, len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } 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();
                }
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("time:" + (end - start));
    }
  • BufferedInputStream/BufferedOutputStream
    这两个是作用于字节流的处理流,它可以加速文件的读取和写入,所以我就尝试了一下。通过对比,我认为是有快一点,决定因素更大的在于byte[]数组的缓冲区大小。据说4k的缓冲区是读写大文件的最快方式。BufferedInputStream/BufferedOutputStream用法也非常简单,其实流族的代码写法都差不太多。下面来看看代码的使用:
@Test
    public void copy2Test() {
        long start = System.currentTimeMillis();
        //备注:mv.mp4  测试文件大小 500M
        File file = new File("mv.mp4");
        File file2 = new File("mv2.mp4");
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            FileInputStream fis = new FileInputStream(file);
            FileOutputStream fos = new FileOutputStream(file2);
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);
            byte[] b = new byte[4 * 1024];// 4k缓冲区
            int len;
            while ((len = bis.read(b)) != -1) {
                bos.write(b, 0, len);
                bos.flush();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //把 bis 和 bos 关闭,那么 fis 和 fos 也就自动被关闭
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("time:" + (end - start));
    }
  • FileReader/FileWriter
    这两个也是节点流,可以直接作用于文件上。但这个只能用于读取/写入纯文本文件(.txt),比如你新建的.doc文件也纯写文字,它也不能给你读取出来。它的用法类似,只是接收的缓冲区改为 char 型,代码如下:
/**
     * 使用 FileReader 来读取文件 注意:只能读取文本文件,如.txt
     */
    @Test
    public void frTest() {
        FileReader fr = null;
        try {
            File file = new File("Hello.txt");
            fr = new FileReader(file);
            char[] c = new char[10];
            int len;
            while ((len = fr.read(c)) != -1) {
                String str = new String(c, 0, len);
                System.out.println(str);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

FileReader/FileWriter实现纯文本文件的拷贝

@Test
    public void txtCopyTest() {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            File file = new File("Hello.txt");
            File file2 = new File("Test.txt");
            fr = new FileReader(file);
            fw = new FileWriter(file2);
 
            char[] c = new char[10];
            int len = 0;
            while ((len = fr.read(c)) != -1) {
                fw.write(c, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • BufferedReader/BufferedWriter
    这两个是作用于字符流的处理流,目的也是加速文件的读取和写入速度。具体用法:

@Test
    public void txtCopy2Test() {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            File file = new File("Hello.txt");
            File file2 = new File("Test.txt");
            FileReader fr = new FileReader(file);
            FileWriter fw = new FileWriter(file2);
            br = new BufferedReader(fr);
            bw = new BufferedWriter(fw);
            String str = null;
            while ((str = br.readLine()) != null) {
                bw.write(str);
                bw.newLine();
                bw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • InputStreamReader/InputStreamWriter

    这是一个转换流,例如从键盘上输入一条字符串,本质是二进制的字符码,我们可以将它转为能看的懂的字符。代码如下:

/**
     * 系统的输入和输出,利用InputStreamReader(转换流)来转二进制码
     */
    @Test
    public void systemOut() {
        InputStreamReader isr = null;
        try {
            System.out.println("请输入字符串:");
            InputStream is = System.in;
            // 用到转换流
            isr = new InputStreamReader(is);
            char[] c = new char[10];
            int len;
            while ((len = isr.read(c)) != -1) {
                String str = new String(c, 0, len);
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (isr != null) {
                try {
                    isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • PrintStream/PrintWriter
    这两个是打印流,用于打印数据。也可以直接作用文件上,例如,向文本文件中打印数据:
    /**
     * PrintStream 和 PrintWriter 用法一致,只不过流的单位不同
     */
    @Test
    public void printIOTest() {
        PrintStream ps = null;
        try {
            File file = new File("Hello2.txt");
            ps = new PrintStream(file);
            ps.append("Java");
            ps.append("Android");
            ps.append("Python");
            ps.append("kotlin");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                ps.close();
            }
        }
    }
  • DataInputStream/DataOutputStream
    这两个流比较特殊,它只能用于读取/写入基本数据类型(byte、char、short、int、long、float、double、boolean),额外还可以写入 String (readUTF)。但是写入的基本数据类型会形成字节码,我们只能通过 DataInputStream来读取。代码例子:
/**
     * DataOutputStream 只能用于写入基本数据类型,反之也只能用DataInputStream来读取,否则读到的就是乱码
     */
    @Test
    public void dataIOTest() {
        DataInputStream dis = null;
        DataOutputStream dos = null;
        try {
            dis = new DataInputStream(new FileInputStream(new File("Hello2.txt")));
            dos = new DataOutputStream(new FileOutputStream(new File("Hello2.txt")));
            dos.writeUTF("Java");
            dos.writeChar('c');
            dos.writeLong(4564114564l);
            dos.writeBoolean(false);
            dos.write(22);
 
            System.out.println(dis.readUTF());
            System.out.println(dis.readChar());
            System.out.println(dis.readLong());
            System.out.println(dis.readBoolean());
            System.out.println(dis.read());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (dos != null) {
                try {
                    dos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (dis != null) {
                try {
                    dis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

使用 FileInputStream 来读取的话,只能读到乱码文件:


使用DataInputStream来都,则正常显示:


  • ObjectInputStream/ObjectOutputStream
    这两个是对象处理流,主要是把对象持久性的存储。比如我有一个Student类,我创建了2个Student类的对象并对它进行了赋值,然后我想存储持久性这个对象,就需要这两个处理流。

  • 序列化:允许把内存中的Java对象转换为二进制流来持久性的储存,通过网络可以传输序列化对象(二进制流)。

  • 反序列化:可以通过网络接收到的序列化对象(二进制流)来恢复原来的Java对象。
    需注意:处理流在存储对象的时候,比如一个类,这个类必须实现序列化接口(Serializable 或 Externalizable)。如果被 static 或 transient 修饰的变量,不可被序列化,接收到的就会是空值。例子如下:

ObjectOutputStream 序列化对象

         /**
     * 序列号过程
     */
    @Test
    public void objIOTest() {
        Student stu1 = new Student("001", "张三");
        Student stu2 = new Student("002", "李四");
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(new File("student.txt")));
            oos.writeObject(stu1);
            oos.flush();
            oos.writeObject(stu2);
            oos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

ObjectInputStream 反序列化

    /**
     * 反序列化
     */
    @Test
    public void objIOTest2() {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(new File("student.txt")));
            Student get_stu1 = (Student) ois.readObject();
            System.out.println(get_stu1);
            Student get_stu2 = (Student) ois.readObject();
            System.out.println(get_stu2);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • RandomAccessFile
    这个流属于比较特殊,它自身可以充当输入流,也可以充当输出流。原因是它的构造器可以传入一种 mode,这种 mode 共分为4种情况:

  • r 只读

  • rw 读和写

  • rwd 读和写,并且同步更新内容

  • rws 读和写,并且同步更新内容和元数据
    例子:通过RandomAccessFile对文本文件的插入操作,RandomAccessFile(以及所有流)在写入的时候,都会将文件中的数据覆盖。所以,通过seek();方法将光标往后移动,保存后面的字符数据后再插入,最后将字符数据加到后面即可。

    /**
     * RandomAccessFile 实现在文本中插入数据
     */
    @Test
    public void randomAccessTest() {
        RandomAccessFile raf_rw = null;
        try {
            raf_rw = new RandomAccessFile(new File("Hello3.txt"), "rw");
            // 光标移动到要插入的位置
            raf_rw.seek(5);
            // 保存后面的所有字符串
            StringBuffer buf = new StringBuffer();
            String str;
            while ((str = raf_rw.readLine()) != null) {
                buf.append(str + "\n");
            }
            System.out.println(buf.toString());
            raf_rw.seek(5);
            raf_rw.writeUTF("Java" + buf.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                raf_rw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

IO流的大致使用都过了一遍,这些仅仅是的基本用法。

最后

最后我准备了一些面试的知识汇总,数据结构,计算机网络等等都有。自己整理和分类的,还请尊重知识产出。
分享给大家的资料包括高级架构技术进阶脑图、Android开发面试专题资料,还有高级进阶架构资料包括但不限于【高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术】希望能帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也是可以分享给身边好友一起学习的!
资料免费领取方式:加群:797404811

image

相关文章

网友评论

    本文标题:什么是 Java 输入输出流?流的用法大全

    本文链接:https://www.haomeiwen.com/subject/iilcsctx.html