Java I/O流基础类介绍

一、全文概览

在我们日常工作中,或多或少会对文件进行各种各样的操作。有些我们是亲自去处理的,例如在我工作中,流量读取策略信息时,我们就是将策略存放到本地文件当中,在生成运行时策略信息的时候,我们会先从本地文件中读取;也有些是我们看不到被框架底层封装的,例如日志框架将日志写入磁盘,日志监控读取磁盘文件等操作。这些I/O流的操作涉及我们工作的方方面面,因此对它进行一定的学习是对我们有很大的帮助的,例如顺序读取、随机访问、缓冲、字符、按行读取、按字读取等等。

本文先从基础的I/O流入手,主要介绍包括基于字节的InputStream、OutputStream;基于字符的Reader、Writer以及自成一派的RandomAccessFile,他们的核心函数以及功能扩展的派生类。

I/O流**加粗样式**

二、InputStream

InputStream用于标识哪些从不同源生成的输入类,可能的源包括以下几种:

  • 字节数组
  • 字符串对象
  • 文件
  • 管道
  • 其他流组成的序列
  • 其他源,例如网络连接

1、InputStream核心函数解析

这里就直接将InputStream抽象类函数粘贴过来,并增加函数功能注释

public abstract class InputStream implements Closeable {

    // MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to
    // use when skipping.
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;

    // 从输入流中读取下一个字节的数据。值字节返回为0到255范围内的整数。如果由于已到达流的结尾而没有字节可用,则返回值-1。
    // 此方法将阻塞,直到输入数据可用、检测到流的结尾或引发异常。
    // 细节由子类实现
    public abstract int read() throws IOException;

    // 从输入流中读取一定数量的字节并将其存储到缓冲区数组b中。实际读取的字节数以整数形式返回。
  	// 此方法将阻塞,直到输入数据可用、检测到文件结尾或引发异常。
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    // 从输入流中读取len长度的字节,并将这些内容存储到缓冲b中off偏移处起
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

    // 在读取字节流时,决定跳过n个字节,但实际上可能会跳过小于n个字节的长度,返回值就是实际跳过的长度
    public long skip(long n) throws IOException {

        long remaining = n;
        int nr;

        if (n <= 0) {
            return 0;
        }

        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;
            }
            remaining -= nr;
        }

        return n - remaining;
    }

    // 返回此输入流在不受阻的情况下能够读取的字节数
    public int available() throws IOException {
        return 0;
    }

    // 关闭此输入流,释放所有该输入流占用的系统资源
    public void close() throws IOException {}

    // 标记当前读取字节流的位置,方便后续reset重置的时候能够返回到相应的位置继续读取数据
    public synchronized void mark(int readlimit) {}

    // 重置
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

    // 表示当前输入流是否支持标记
    public boolean markSupported() {
        return false;
    }

}

2、ByteArrayInputStream介绍

InputStream的实现类之一,它是针对字节源生成的输入流,可以使内存中的缓冲区充当InputStream,构造参数为用于提取出字节的缓冲区,通过该类链接到FilterInputStream对象中来提供有用的接口

package com.markus.java.io.inputstream;

import java.io.ByteArrayInputStream;
import java.io.IOException;

/**
 * @author: markus
 * @date: 2023/2/5 1:48 PM
 * @Description: 基于字节数组数据源的输入类示例
 * @Blog: http://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class ByteArrayInputStreamDemo {
    public static void main(String[] args) throws IOException {
        byte[] source = {1, 2, 3, 4, 5, 6, 7, 8, 9};
        ByteArrayInputStream bis = new ByteArrayInputStream(source);

        byte[] dest = new byte[source.length];
        // read 为读取缓冲区的总字节数,如果没有多余可读的字节则返回-1
        int read = bis.read(dest);
        System.out.println(read);
        for (byte b : dest) {
            System.out.print(b + " ");
        }
    }
}

3、StringBufferInputStream介绍-(官方已废弃)

针对字符源构建的输入流

package com.markus.java.io.inputstream;

import java.io.IOException;
import java.io.StringBufferInputStream;

/**
 * @author: markus
 * @date: 2023/2/5 2:24 PM
 * @Description: 构造字符源的输入流演示
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class StringBufferInputStreamDemo {
    public static void main(String[] args) throws IOException {
        String text = "Hello,IO";
        StringBufferInputStream stringIs = new StringBufferInputStream(text);
        int destLength = stringIs.available();
        byte[] dest = new byte[destLength];
        int len = stringIs.read(dest);
        System.out.println(len);
        for (byte b : dest) {
            System.out.print(b + " ");
        }
    }
}
// 控制台
/**
 * 8
 * 72 101 108 108 111 44 73 79 
 * Process finished with exit code 0
 */

4、FileInputStream介绍

FileInputStream是基于文件源来构造的输入源,也是我们在工作中经常见到的类

package com.markus.java.io.inputstream;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author: markus
 * @date: 2023/2/5 2:30 PM
 * @Description: 构造文件源的输入流演示
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class FileInputStreamDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("/Users/zhangchenglong/IdeaProjects/OnJava8-Examples/io/src/main/resources/file.txt");
        FileInputStream fileIs = new FileInputStream(file);
        long fileLen = file.length();
        int destLength = fileIs.available();
        byte[] dest = new byte[destLength];
        int readLen = fileIs.read(dest);
        System.out.println(fileLen);
        System.out.println(destLength);
        System.out.println(readLen);

        for (byte b : dest) {
            System.out.print(b + " ");
        }
    }
}
// 文件内容
/**
 * Hello,FileInputStream
 */
// 控制台
/**
 * 21
 * 21
 * 21
 * 72 101 108 108 111 44 70 105 108 101 73 110 112 117 116 83 116 114 101 97 109 
 * Process finished with exit code 0
 */

5、PipedInputStream介绍

PipedInputStream是基于管道源构造的输入流。所谓”管道“,其运行机制就像是物理管道一样:将物体放入一端,然后它会从管道的另一端出来

package com.markus.java.io.inputstream;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

/**
 * @author: markus
 * @date: 2023/2/5 2:42 PM
 * @Description: 构造管道源的输入流演示
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class PipedInputStreamDemo {
    public static void main(String[] args) throws IOException {
        PipedOutputStream pipedOs = new PipedOutputStream();
        PipedInputStream pipedIs = new PipedInputStream(pipedOs);

        byte[] source = {1, 2, 3, 4, 5};
        pipedOs.write(source);

        int destLen = pipedIs.available();
        byte[] dest = new byte[destLen];
        int readLen = pipedIs.read(dest);
        System.out.println(destLen);
        System.out.println(readLen);

        for (byte b : dest) {
            System.out.print(b + " ");
        }
    }
}
/**
 * 5
 * 5
 * 1 2 3 4 5 
 * Process finished with exit code 0
 */

6、SequenceInputStream介绍

SequenceInputStream是以多流序列构造的输入流,其功能就是将两个以上的InputStream转换为单个InputStream进行后续操作

package com.markus.java.io.inputstream;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * @author: markus
 * @date: 2023/2/5 2:50 PM
 * @Description: 构造其他流序列作为源的输入流演示
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class SequenceInputStreamDemo {
    public static void main(String[] args) throws IOException {
        InputStream is1 = new ByteArrayInputStream(new byte[]{1, 2, 3, 4, 5});
        InputStream is2 = new ByteArrayInputStream(new byte[]{6, 7, 8, 9, 10});
        SequenceInputStream sequenceIs = new SequenceInputStream(is1, is2);

        List<Integer> bytes = new ArrayList<>();
        int temp;
        while ((temp = sequenceIs.read()) != -1) {
            bytes.add(temp);
        }
        System.out.println(bytes.size());
        for (Integer aByte : bytes) {
            System.out.print(aByte + " ");
        }
    }
}
/**
 * 10
 * 1 2 3 4 5 6 7 8 9 10 
 * Process finished with exit code 0
 */

7、FilterInputStream介绍

FilerInputStream是InputStream的实现类,该类也是一个基类,并无实际意义,有意义的是它的派生类可以组合上面6个源的输入流来组成一个新的流进而提供更具意义的输入流接口,它的子类如下:

  • DataInputStream 与DataOutputStream配合使用,以可移植的方式从李璐中读取基本类型(int、char、long等等),提供读取相应基本类型的接口
  • BufferedInputStream 作为缓存流来提高输入流文件的处理效率,用于防止在每次需要更多数据时都进行物理上的读取。相当于声明”使用缓冲区“,它本质上没有额外提供接口,只是为进程增加缓冲操作而已,需要与接口对象配合使用
  • ~~LineNumberInputStream~ 记录输入流中的行号,可以调用getLineNumber和setLineNumber方法,只是增加了行号的接口,因此在操作时可能需要与接口对象搭配使用(官方已废弃)
  • PushbackInputStream 包含一个单字节回退缓冲区,用于将最后读取的字节推回输入流中,通常用语编译器的扫描器,一般业务中很少使用
package com.markus.java.io.inputstream;

import java.io.*;

/**
 * @author: markus
 * @date: 2023/2/5 3:14 PM
 * @Description: FilterInputStreamDemo
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class FilterInputStreamDemo {
    public static void main(String[] args) throws IOException {
        System.out.println("===================DataInputStream======================");
        displayDataInputStream();
        System.out.println("===================BufferedInputStream====================");
        displayBufferedInputStream();
        System.out.println("===================NumberInputStream====================");
        displayLineNumberInputStream();
        System.out.println("===================PushbackInputStream====================");
        displayPushbackInputStream();
    }

    public static void displayDataInputStream() throws IOException {
        byte[] dataSource = new byte[]{1, 0, 0, 0, 10};
        ByteArrayOutputStream byteArrayOs = new ByteArrayOutputStream();
        DataOutputStream dataOs = new DataOutputStream(byteArrayOs);
        dataOs.write(dataSource);


        DataInputStream byteArrayIs = new DataInputStream(new ByteArrayInputStream(byteArrayOs.toByteArray()));
        System.out.println(byteArrayIs.readBoolean());
        System.out.println(byteArrayIs.readInt());

    }

    public static void displayLineNumberInputStream() {
//        LineNumberInputStream
    }

    public static void displayBufferedInputStream() throws IOException {
        BufferedInputStream bufferedIs = new BufferedInputStream(new FileInputStream(new File("/Users/zhangchenglong/IdeaProjects/OnJava8-Examples/io/src/main/java/com/markus/java/io/inputstream/FilterInputStreamDemo.java")));
    }

    public static void displayPushbackInputStream() throws IOException {
        PushbackInputStream pushbackIs = new PushbackInputStream(new ByteArrayInputStream(new byte[]{1, 2, 3, 4}));

        System.out.println(pushbackIs.read());
        pushbackIs.unread(1);
        System.out.println(pushbackIs.read());
    }
}
/**
 * ===================DataInputStream======================
 * true
 * 10
 * ===================BufferedInputStream====================
 * ===================NumberInputStream====================
 * ===================PushbackInputStream====================
 * 1
 * 1
 *
 * Process finished with exit code 0
 */

三、OutputStream

与InputStream相反,OutputStream系列的类则是决定输出的去向:

  • 字节数组
  • 文件
  • 管道

1、ByteArrayOutputStream介绍

ByteArrayOutputStream是在内存中创建一块缓冲区,所有发送到流中的数据都被放在该缓冲区中。

package com.markus.java.io.outputstream;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * @author: markus
 * @date: 2023/2/5 4:10 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class ByteArrayOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        ByteArrayOutputStream byteArrayOs = new ByteArrayOutputStream();
        byte[] bytes = {1, 2, 3, 4, 5};
        byteArrayOs.write(bytes);

        byte[] memory = byteArrayOs.toByteArray();
        for (byte b : memory) {
            System.out.print(b + " ");
        }
    }
}
/**
 * 1 2 3 4 5 
 * Process finished with exit code 0
 */

2、FileOutputStream介绍

FileOutputStream用于向文件发送信息

package com.markus.java.io.outputstream;

import java.io.*;

/**
 * @author: markus
 * @date: 2023/2/5 4:13 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class FileOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        FileOutputStream fileOs = new FileOutputStream(new File("/Users/zhangchenglong/IdeaProjects/OnJava8-Examples/io/src/main/resources/fileFromOutputStream.txt"));
        fileOs.write(new byte[]{1, 2, 3, 4, 5});

        FileInputStream fileIs = new FileInputStream(new File("/Users/zhangchenglong/IdeaProjects/OnJava8-Examples/io/src/main/resources/fileFromOutputStream.txt"));
        int read;
        while ((read = fileIs.read()) != -1) {
            System.out.print(read + " ");
        }
    }
}
/**
 * 1 2 3 4 5 
 * Process finished with exit code 0
 */

3、PipedOutputStream介绍

PipedOutputStream用于向管道的一端写入字节数据,与PipedInputStream配合使用,实现管道传输,代码可参考PipedInputStream介绍

4、FilterOutputStream介绍

FilterOutputStream与FilterInputStream作用一致,作为装饰器接口的抽象类,装饰器用来为其他OutputStream类提供有用的功能,具体的装饰器类如下:

  • DataOutputStream 与DataInputStream搭配使用,这样就能以可移植的方式向流中写入基本类型
  • PrintStream 用于生成格式化的输出。DataOutputStream负责处理数据的存储,而PrintStream则用来负责数据的展示,例如我们常用的System.out对象就是PrintStream
  • BufferedOutputStream 用来防止在每次发送数据的时候都发生物理写操作。相当于声明”使用缓冲“。可以调用flush来清空缓冲区

四、Reader和Writer

Reader、Writer是读取/写入字符流的抽象类,乍一看我们以为是用来替换InputStream和OutputStream,但实际上则是提供了兼容Unicode并且基于字符的I/O能力。虽然原始的流库在某些方面已被启用,但是InputStream和OutputStream类仍然以面向字节的I/O流提供了有价值的功能。具体的Reader和Writer如下:

  • Reader
    • InputStreamReader
    • FileReader
    • StringReader
    • CharArrayReader
    • PipedReader
    • FilterReader
      • BufferedReader
      • LineNumberReader
      • PushbackReader
  • Writer
    • OutputStreamWriter
    • FileWriter
    • StringWriter
    • CharArrayWriter
    • PipedWriter
    • FilterWriter
    • BufferedWriter
    • PrintWriter

上面的InputStreamReader、OutputStreamWriter则是对InputStream、OutputStream的适配,在我们工作中有时必须将字节流和字符流结合起来使用,所以增加适配器类是有必要的。

下面我们以FileReader和FileWriter为例,简单示范一下Reader和Writer的使用方式,其他Reader或者Writer类使用方式有兴趣大家可以私下尝试下,我这里就不做赘述了。

package com.markus.java.io.reader_writer;

import java.io.*;

/**
 * @author: markus
 * @date: 2023/2/5 4:40 PM
 * @Description: FileReader、FileWriter使用示例
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class FileReaderAndWriterDemo {
    public static final String PATH_NAME = "/Users/zhangchenglong/IdeaProjects/OnJava8-Examples/io/src/main/resources/file_writer.txt";

    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter(PATH_NAME);
        fw.write("Hello,IO");
        fw.flush();
        fw.close();

        FileInputStream fileOs = new FileInputStream(new File(PATH_NAME));
        int len = fileOs.available();
        InputStreamReader fr = new InputStreamReader(fileOs);
        char[] chars = new char[len];
        int readLen = fr.read(chars);
        fr.close();
        fileOs.close();

        System.out.println(new String(chars));
    }
}
/**
 * Hello,IO
 *
 * Process finished with exit code 0
 */

六、RandomAccessFile

RandomAccessFile适合用来处理大小已知的记录组成的文件,由此可以通过seek在各条记录上来回移动,然后读取或者修改记录。文件中的各条记录大小不必相同,只需要确定它们的大小以及在文件中的位置即可。我们查看类结构,发现RandomAccessFile并没有继承InputStream或者OutputStream,类比来看,它有点类似于DataInputStream和DataOutputStream结合起来使用,再加上几个方法:getFilePointer-获取当前所处的位置、seek-移动到文件中的某个文职、length-判断文件的最大大小。

package com.markus.java.io;

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

/**
 * @author: markus
 * @date: 2023/2/5 4:58 PM
 * @Description: RandomAccessFile使用演示
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class RandomAccessFileDemo {
    public static void main(String[] args) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(new File("/Users/zhangchenglong/IdeaProjects/OnJava8-Examples/io/src/main/resources/RandomAccessFile.txt"), "rw");
        raf.writeDouble(1d);
        raf.writeDouble(2d);
        raf.writeInt(1);
        raf.writeUTF("Hello,RandomAccessFile");
        raf.close();

        raf = new RandomAccessFile(new File("/Users/zhangchenglong/IdeaProjects/OnJava8-Examples/io/src/main/resources/RandomAccessFile.txt"), "rw");
        System.out.println(raf.readDouble());
        raf.seek(16);
        System.out.println(raf.readInt());
        System.out.println(raf.readUTF());
        System.out.println(raf.getFilePointer());
        raf.close();
    }
}
/**
 * 1.0
 * 1
 * Hello,RandomAccessFile
 * 44
 *
 * Process finished with exit code 0
 */

七、全文总结

Java I/O流库满足了基本的需求:可以对控制台、文件、内存甚至跨网络进行读写操作。但使用过程中的感受是复杂的,它的功能很多,并且很轻量。使用之前我们应该先理解下装饰器模式,这会大大提高我们学习I/O流类的效率。

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值