(二)、Java I/O系统——字节流

在第一节中,我们了解了 File 类对于文件的操作。File对象封装了文件或者路径属性,但是不包括从/向文件读/写数据的方法,本节主要讲解Java的字节流。
I/O流解决的问题:设备与设备之间数据传输的问题。
Java流的分类:

  1. 从流的方向上可以分为输入流和输出流,应用程序通过输入流读取数据,通过输出流发送数据。
  2. 按照数据传输单位可以划分为字节流和字符流。
    字节流读取的是文件中的二进制数据,不会对数据进行任何处理。
    字符流读取的也是文件中的二进制数据,但是会对这些数据进行处理(解码)

Java I/O类的设计是应用继承一个很好的例子,使用装饰者模式进行设计。关于I/O中装饰者模式的内容请关注后续章节。

1、字节流

这里写图片描述

1.1、FileInputStream

构造方法:

  • FileInputStream(File file) 由file对象创建一个FileInputStream
  • FileInputStream(String filename) 由文件名创建一个FileInputStream

    1. int read() 从输入流中读取一个字节的数据,返回读取的数据,如果达到输入流末尾,则返回-1。
    2. int read(byte[] buffer) 从输入流中读取b.length个字节的数据到字节数组buffer中,返回实际读取的字节数。如果到达输入流末尾,则返回-1。
    3. int read(byte[] buffer,int off,int len) 从输入流中读取多个字节的数据,并存储在buffer[off]~buffer[off+len-1]中,返回实际读取的字节数。如果到达输入流末尾,则返回-1。

最常见的FileInputStream读取文件的方式:

private static void readFile() {

        FileInputStream fis = null;

        File f = new File("./src/a.txt");
        try {
            fis = new FileInputStream(f);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        byte[] buf = new byte[4];  // 一次性读取buf.length个字节的数据
        int length = 0;
        try {
            while ((length = fis.read(buf)) != -1) {  //将读取的字节数据装入byte数组中,返回的是读取的字节数;读至文件末尾返回 -1
                /**
                 * read方法将数据读进byte数组采用覆盖的方式,而并非清空重新赋值的方式。
                 * 故,在获取输出时要指定byte数组中有效的元素。
                 */
                System.out.print(new String(buf, 0, length));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

int read(byte[] b) 方法将数据读进byte数组中,采用覆盖的方式,并不是清空后重新添加的方式。所以从byte数组中获取输出数据时需要指定有效元素。
使用流读取文件后务必要关闭资源 fis.close()

1.2、FileOutputStream

构造方法:

  • FileOutputStream(File file) 由file对象创建一个FileOutputStream
  • FileOutputStream(String filename) 由文件名创建一个FileOutputStream
  • FileOutputStream(File file,boolean append) append为真,数据追加至文件末尾
  • FileOutputStream(File file,boolean append) append为真,数据追加至文件末尾

    1. void write(int b) 将指定字节写入输出流
    2. void write(byte[] b) 将字节数组b中的所有字节写入输出流
    3. void write(byte[] b,int off,int len) 将b[off]~b[off+len-1]写入输出流
    4. void close() 关闭输出流并释放与其有关的系统资源
    5. void flush() 刷新输出流并强制写出所有缓冲的输出字节

常见的使用FileOutputStream写文件的方法:

private static void writeFile() throws IOException {

        FileOutputStream fos = null;

        File file =  new File("./src/b.txt");
        fos = new FileOutputStream(file,true);
        String data = "xiaopeng";
        fos.write(data.getBytes());   //写入字节数组

        fos.close();
    }

write(int b)方法传入int类型的变量(4个字节),但是该方法只会将最低位的那个字节写入文件,其余的三个字节会被舍弃。二进制数:00000000-00000000-00000001-01100001(353),将353写入,但是文件中却只有低八位(97)。

private static void writeTest() throws IOException {

        FileOutputStream fos = null;
        FileInputStream fis = null;
        File file = new File("./src/b.txt");
        fos = new FileOutputStream(file);
        fis = new FileInputStream(file);
        fos.write(353);   //00000000-00000000-00000001-01100001
        byte[] bytes = new byte[4];
        fis.read(bytes);

        System.out.println(Arrays.toString(bytes));

        fis.close();
        fos.close();
    }

输出:

[97, 0, 0, 0]

1.3、BufferedInputStream/BufferedOutputStream

Java提供了专门的缓冲字节流来提高文件读取的效率。BufferedInputStream/BufferedOutputStream类内部有一个缓冲字节数组(默认8192B),用来提高处理效率。

private static void bufferedReadTest() throws IOException {

        BufferedInputStream bufferedInputStream = null;
        //BufferedInputStream本身不具备读取文件的功能,需要借助FileInputStream进行读取文件
        bufferedInputStream = new BufferedInputStream(new FileInputStream(new File("./src/a.txt")));

        int content = 0;
        while((content = bufferedInputStream.read()) != -1){
            System.out.print((char)content);
        }

        bufferedInputStream.close();  //内部其实调用 fileInputStream.close()
    }

BufferedInputStream不具备读取文件的功能,需要借助FileInputStream进行文件读取,查看BufferedInputStream的read方法可以发现:

public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }
  • count:代表BufferedInputStream内部维护的字节数组大小
  • pos:代表当前读取字节数组中那个下标的数据
    若pos<=count,那么调用 fill() 方法(其实是FileInputStream的int read(byte[] b,int off,int len))将数据读入字节数组;否则直接从字节数组中读取一个字节的数据并返回。

BufferedInputStream的 close() 方法其实内部调用传入的FileInputStream的 close() 方法,因此关闭资源时只需要调用BufferedInputStream即可。

public void close() throws IOException {
        byte[] buffer;
        while ( (buffer = buf) != null) {
            if (bufUpdater.compareAndSet(this, buffer, null)) {
                InputStream input = in;
                in = null;
                if (input != null)
                    input.close();    //FileInputStream.close()
                return;
            }
        }
    }

关于BufferedOutputStream将数据写入磁盘的实现方式:

  1. 调用父类FilterOutputStream的 close() 方法关闭资源:
public void close() throws IOException {
        try (OutputStream ostream = out) {
            flush();
        }
    }
  1. 手工调用BufferOutputStream的 flush() 方法将数据写入:
public synchronized void flush() throws IOException {
        flushBuffer();
        out.flush();
    }
  1. 查看BufferedOutputStream的 write() 方法可以发现,当缓冲字节数组容量满时,也会调用将数据写入:
public synchronized void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();  //实际调用FilterOutputStream.write()写入文件
        }
        buf[count++] = (byte)b;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章