Java IO知识梳理


一、随机文件流

1. 模式介绍

RandomAccessFile 独立于基本的 IO 流,是专门为文件读写提供的一种设计,与之前的提到的不同的是 RandomAccessFile 可通过指定模式使之既可读又可写,从而提高代码效率。

在初始化对象时传入文件路径并指定操作模式,其中操作模式包含下列四种

(1) r 模式

以只读方式打开指定文件,如果试图对该 RandomAccessFile 指定的文件执行写入方法则会抛出 IOException

(2) rw 模式

以读取、写入方式打开指定文件,如果该文件不存在,则尝试创建文件。

(3) rws 模式

以读取、写入方式打开指定文件,相对于 rw 模式,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备,默认情形下(rw模式下)是使用 buffer 的,只有 cache 满的或者使用 close() 时候才真正的写到文件。

(4) rwd 模式

rws 类似,只是仅对文件的内容同步更新到磁盘,而不修改文件的元数据。

2. 基本操作

RandomAccessFile 类的基本基本使用示例如下:

public void initDemo() {
    String location = "src\\main\\resources\\info.txt";
    try (RandomAccessFile raf = new RandomAccessFile(location, "rw")) {
        // 获取当前指针位置
        raf.getFilePointer();
        // 设置当前指针位置
        raf.seek(10);
        // 读取单字节数据
        raf.read();
        // 写入内容
        raf.write("message".getBytes());
        // 读取 int 值, 同理还有 readLong() 等等
        raf.readInt();
        // 写入 int 值, 同理还有 writeLong() 等等
        raf.writeInt(1);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

3. 文件读取

通过 RandomAccessFile 实现本地文件内容读取。

public void readDemo() {
    String sourcePath = "src\\main\\resources\\info.txt";
    try (RandomAccessFile raf = new RandomAccessFile(sourcePath, "r")) {
        int ch;
        // 设置每次读取大小
        byte[] buffer = new byte[1024];
        while ((ch = raf.read(buffer)) != -1) {
            System.out.write(ch);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

4. 文件写入

通过 RandomAccessFile 实现内容数据的写入。

public void writeDemo() {
    String msg = "The message from RandomAccessFile.";
    String targetPath = "src\\main\\resources\\info.txt";
    try (RandomAccessFile raf = new RandomAccessFile(targetPath, "w")) {
        // Write data
        raf.write(msg.getBytes());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

二、基本输入流

1. InputStream

InputStream 是所有输入流的父类,提供基础的 IO 读取服务,通过 read() 方法即可读取文件内容,当读到最后一位时返回 -1

注意 InputStream 为无缓冲读取,即读取的字节内容后需要立即写入,在读取大文件时则会导致产生大量的磁盘 IO 操作,因此在实际应用中使用更多的通常是后续提到的缓存流。

InputStream 较为常见的读取方式有如下两种:

  • read(): 一次只能读取一个字节数据,当内容较大时则会产生较多的 IO 操作从而影响性能。
  • read(byte[]): 可指定单次读取的字节大小,通过增加单次读取的数量从而降低读取频次,进而提升性能。
public void inputDemo() {
    File file = new File("src\\main\\resources\\info.txt");
    try (InputStream in = new FileInputStream(file)) {
        // read single byte
        in.read();
        // read with buffer
        in.read(new byte[1024]);
        // read specify size from byte array
        in.read(new byte[1024], 0, 1024);
        // skip specify size from resource
        in.skip(4);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2. FileInputStream

FileInputStream 继承自 InputStream ,用于读取文件内容,其提供两类文件初始化方式,选择传入文件路径或传入 File 类。

下述示例即为一个简单的文件内存读取输出样例:

public void fileInputDemo(){
    File file = new File("src\\main\\resources\\info.txt");
    try(FileInputStream is = new FileInputStream(file)) {
        int ch;
        // batch: 单次读取大小
        byte[] batch = new byte[256];
        while ((ch = is.read(batch)) != -1) {
            System.out.write(ch);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

3. InputStreamReader

InputStreamReader 是字节流 (byte) 与字符流 (char) 之间的桥梁,能将字节流输出为字符流,并且能为字节流指定字符集,可输出一个个的字符。

字节流操作汉字或特殊符号语言的时候容易乱码,因此读取文本时可使用字符流实现,但操作二进制文件(比如图片、音频、视频)必须使用字节流。

public void readerDemo(){
    File file = new File("src\\main\\resources\\info.txt");
    try (
            FileInputStream fis = new FileInputStream(file);
            InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)
    ) {
        int ch;
        while ((ch = isr.read()) != -1) {
            System.out.write(ch);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

4. DataInputStream

DataInputStream 支持以 Java 原始数据类型的形式从输入流中读取数据,提供了一组 readXXX() 方法读取不同类型的原始数据类型。

public void dataInputStreamDemo() {
    File file = new File("src\\main\\resources\\info.txt");
    try (
            FileInputStream fis = new FileInputStream(file);
            DataInputStream dis = new DataInputStream(fis)
    ) {
        int ch;
        while ((ch = dis.read()) != -1) {
            System.out.write(ch);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

5. ByteArrayInputStream

ByteArrayInputStream 用于读取内存数据,可以从字节数组中读取数据。

public void byteInputStreamDemo() {
    String msg = "The message from ByteArrayInputStream.";
    byte[] data = msg.getBytes();
    try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
        int ch;
        while ((ch = bis.read()) != -1) {
            System.out.write(ch);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

三、基本输出流

1. OutputStream

OutputStream 为所有输出流的父类,可以实现数据的写入服务,与 InputStream 一样其同样没有缓冲机制会导致大量 IO 操作。

通过 write() 方法即可实现数据的写入,与 read() 类似可通过传入 byte 数组一次性写入多字节数据。

public void outputStreamDemo() {
    String location = "src\\main\\resources\\info.txt";
    try (FileOutputStream fos = new FileOutputStream(location)) {
        // write single data
        fos.write(10);
        // write specify byte
        fos.write(new byte[1024]);
        // write specify size from byte array
        fos.write(new byte[1024], 0, 1024);
        // send cache data to destination
        out.flush();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2. FileOutputStream

FileOutputStream 继承于 OutputStream,可以实现文件媒体文件的写入服务。

需要注意其只能写入字节数据,如将字符串写入文件需要先将其转为 byte 类型。

public void fileOutputDemo(){
    String location = "src\\main\\resources\\info.txt";
    String message = "Message from FileOutputStream.";
    byte[] bytes = message.getBytes();        
    try (OutputStream out = new FileOutputStream(location)) {
        // write data
        out.write(bytes);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    System.out.println("write complete");
}

3. OutputStreamWriter

OutputStreamWriterInputStreamReader 相对应,可以直接将字符直接写入文件。

public void fileOutputWriteDemo() {
    String location = "src\\main\\resources\\info.txt";
    try (
            FileOutputStream fos = new FileOutputStream(location);
            OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8")
    ) {
        // Don't have to convert data type
        osw.write(message);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    System.out.println("write complete");
}

4. DataOutputStream

DataOutputStreamDataInputStream 类似,允许以机器无关方式的方式从写入基本 Java 数据类型。

5. ByteArrayOutputStream

ByteArrayOutputStream 可用于往内存写入数据。

四、缓冲输入流

1. BufferedReader

BufferedReader 字符缓冲输入流,提供通用的缓冲方式文本读取。

通过 readLine() 读取一个文本行,从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。

public void bufferReadDemo(){
    File file = new File("src\\main\\resources\\info.txt");
    try (
            FileReader fr = new FileReader(file);
            BufferedReader bf = new BufferedReader(fr)
    ) {
        String line;
        StringBuilder builder = new StringBuilder();
        while ((line = bf.readLine()) != null) {
            builder.append(line);
        }
        System.out.println(builder);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2. BufferedInputStream

在之前介绍了 InputStream 是与目标对象建立了一个连接,再通过 read()write() 进行数据的读取与写入,当读取到数据后必须立即进行写入操作。

BufferedInputStream 则在内部自动维护大小为 8192 的缓存区,如果缓存区没有数据或者数据不足才会从底层 InputStream 中读取数据,能有效的减少磁盘 IO 操作从而提高性能。

public void fileInputDemo(){
    File sourceFile = new File("src\\main\\resources\\info.txt");
    try (
            FileInputStream fis = new FileInputStream(sourceFile);
            BufferedInputStream bis = new BufferedInputStream(fis)
    ) {
        int ch;
        byte[] buffer = new byte[1024];
        while ((ch = bis.read(buffer)) != -1) {
            System.out.write(buffer, 0, chs);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

五、缓冲输出流

1. BufferedWriter

BufferedWriterBufferedReader 对应,提供带缓存区的写入。

public void bufferWriteDemo() {
    String msg = "This is a test from BufferedWriter.";
    File file = new File("src\\main\\resources\\test.txt");

    try (
            FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw)
    ) {
        bw.append(msg);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    System.out.println("write complete");
}

2. BufferedOutputStream

BufferedOutputStreamBufferedInputStream 对应,内部实现了一个缓冲区数组从而实现更高效的写入服务。

public void bufferedOutputStreamDemo() {
    String msg = "This is message from BufferedOutputStream.";
    File file = new File("src\\main\\resources\\test.txt");

    try (
            FileOutputStream fos = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(fos)
    ) {
        bos.write(msg.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println("Message 4 write complete.");
}

六、数据序列化

在计算机中任何数据都是以二进制的形式存储,因此当项目中涉及到数据对象的传输存储时,通过我们需要将复杂的对象数据转化为字节数据,下面介绍几类常见的对象数据转化。

1. Object转Byte

Java 对象序列化为 byte 数组。

public <T> byte[] objectToByte(T t) {
    byte[] bytes = null;
    try (
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bos)
    ) {
        out.writeObject(t);
        out.flush();
        bytes = bos.toByteArray();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return bytes;
}

2. Byte转Object

byte 数组反序列化为 Java 对象。

public <T> T byteToObject(byte[] bytes, Class<T> tClass) {
    Object obj;
    try (
            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bis)
    ) {
        obj = ois.readObject();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return (T) obj;
}

3. Byte转InputStream

byte 数组转为 InputStream 流对象。

public InputStream byteToInputStream(byte[] bytes) {
    return new ByteArrayInputStream(bytes);
}

4. InputStream转Byte

InputStream 转为 byte 数组。

public byte[] inputStreamToByte(InputStream in) {
    try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
        int ch;
        byte[] buff = new byte[100];
        while ((ch = in.read(buff, 0, 100)) > 0) {
            bos.write(buff, 0, ch);
        }
        return bos.toByteArray();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

5. Byte转OutputStream

byte 数组转为 OutputStream 流对象。

public OutputStream byteToOutputStream(byte[] bytes) {
    try (OutputStream out = new ByteArrayOutputStream()) {
        out.write(bytes);
        return out;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录