让你再也忘不了IO相关知识-Java IO图文详解

语言: CN / TW / HK

theme: cyanosis highlight: a11y-dark


1 装饰模式

Java中IO使用的是装饰模式,装饰模式在Android中很常见,比如系统的Context

装饰模式的模型

  • Component:抽象构建接口。
  • ConcreteComponent:具体的构建对象,实现组件对象接口,通常就是被装饰的原始对象。就对这个对象添加功能。
  • Decorator:所有装饰器的抽象父类,需要定义 一个与组件接口一致的接口,内部持有一个Component对象,就是持有一个被装饰的对象。
  • ConreteDecoratorA/ConreteDecoratorB:实际的装饰器对象,实现具体添加功能。

2 流式部分

2.1 I/O体系

IO流需要站在内存的角度去理解:读入写出

读入:将文件从外部读入内存中。

写出:从内存中生成一个文件。

2.2 字节流

BufferInputStream+FileInputStream

Buffer:缓存,作用是提升性能,原理是减少磁盘的磁头操作次数。

只能读写byte类型的数据。

Java File file = new File("txt/BufferedStreamTest.txt"); FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); bis.read();

BufferOutputStream+FileOutputStream

Java File file = new File("txt/BufferedStreamTest.txt"); FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos); bos.write(byteArray[0]); bos.write(byteArray, 1, byteArray.length - 1); //flush()的作用是强制进行一次IO,比如最后写出的数据不够缓存指定的长度,就需要强制的执行一次IO bos.flush(); bos.close();

完整示例:

```Java package site.exciter.learn.io;

import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException;

public class BufferedStreamTest { private static final byte[] byteArray = { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A };

public static void main(String[] args) {
    testBufferedInputStream();

// testBufferedOutputStream();

}

private static void testBufferedInputStream() {
    try {
        File file = new File("txt/BufferedStreamTest.txt");
        FileInputStream fis = new FileInputStream(file);
        BufferedInputStream bis = new BufferedInputStream(fis);

        //读取前10个字节并输出在控制台
        for (int i = 0; i < 10; i++) {
            if (bis.available() >= 0) {
                System.out.println(byteToString((byte) bis.read()));
            }
        }

        //在此输入流中标记当前的位置,对reset方法的后续调用会在最后标记的位置重新定位此流,以便后续重新读取相同的字节。
        bis.mark(2000);
        //跳过并从输入流中丢弃10字节的数据。
        bis.skip(10);

        //跳10个字节之后读取剩余的部分
        byte[] b = new byte[1024];
        int n1 = bis.read(b, 0, b.length);
        System.out.println("n1的值为:" + n1);
        printByteValue(b);

        //调用reset之后,再次读取会从mark标记的位置开始,也就是从第10个字节开始读取。
        bis.reset();
        int n2 = bis.read(b, 0, b.length);
        System.out.println("n2的值为:" + n2);
        printByteValue(b);


    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static void testBufferedOutputStream() {
    try {
        File file = new File("txt/BufferedStreamTest.txt");
        FileOutputStream fos = new FileOutputStream(file);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        bos.write(byteArray[0]);
        bos.write(byteArray, 1, byteArray.length - 1);
        bos.flush();
        bos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static String byteToString(byte b) {
    byte[] bArray = {b};
    return new String(bArray);
}

private static void printByteValue(byte[] buf) {
    for (byte b : buf) {
        if (b != 0) {
            System.out.println(byteToString(b) + " ");
        }
    }
}

} ```

DataInputStream+BufferedInputStream+FileInputStream

可以读写所有基本类型的数据。

Java File file = new File("txt/DataStreamTest.txt"); FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); DataInputStream dis = new DataInputStream(bis);

DataOutputStream+BufferedOutputStream+FileOutputStream

Java File file = new File("txt/DataStreamTest.txt"); FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos); DataOutputStream dos = new DataOutputStream(bos);

完整示例:

```Java package site.exciter.learn.io;

import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException;

public class DataStreamTest { public static void main(String[] args) { // testDataInputStream(); testDataOutputStream(); }

private static void testDataInputStream() {
    try {
        File file = new File("txt/DataStreamTest.txt");
        FileInputStream fis = new FileInputStream(file);
        BufferedInputStream bis = new BufferedInputStream(fis);
        DataInputStream dis = new DataInputStream(bis);

        System.out.println(Long.toHexString(dis.readLong()));
        System.out.println(dis.readBoolean());
        System.out.println(byteToString(dis.readByte()));
        System.out.println(charToString(dis.readChar()));
        System.out.println(shortToString(dis.readShort()));
        System.out.println(Integer.toHexString(dis.readInt()));
        System.out.println(Long.toHexString(dis.readLong()));
        System.out.println(dis.readUTF());
        System.out.println(Long.toHexString(dis.readLong()));

        dis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static void testDataOutputStream() {
    try {
        File file = new File("txt/DataStreamTest.txt");
        FileOutputStream fos = new FileOutputStream(file);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        DataOutputStream dos = new DataOutputStream(bos);

        dos.writeBoolean(true);
        dos.writeByte((byte) 0x41);
        dos.writeChar((char) 0x4243);
        dos.writeShort((short) 0x4445);
        dos.writeInt(0x12345678);
        dos.writeLong(0x987654321L);

        dos.writeUTF("abcdefg");
        dos.writeLong(0x23433L);

        dos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static String byteToString(byte val) {
    return Integer.toHexString(val & 0xff);
}

private static String charToString(char val) {
    return Integer.toHexString(val);
}

private static String shortToString(short val) {
    return Integer.toHexString(val & 0xffff);
}

} ```

ObjectInputStream+BufferedInputStream+FileInputStream

可以读写对象,包含ObjectArrayList等。

```Java FileInputStream fis = new FileInputStream(newFile("txt/Object.txt")); BufferedInputStream bis = new BufferedInputStream(fis); ObjectInputStream ois = new ObjectInputStream(bis);

while (ois.available() != -1) { Object object = ois.readObject(); Book book = (Book) object; System.out.println(book.toString()); }

ois.close(); ```

ObjectOutputStream+BufferedOutputStream+FileOutputStream

```Java FileOutputStream fos = new FileOutputStream("txt/Object.txt"); BufferedOutputStream bos = new BufferedOutputStream(fos); ObjectOutputStream oos = new ObjectOutputStream(bos);

for (int i = 0; i < 10; i++) { oos.writeObject(new Book("三国演义", 102.0f)); }

oos.close(); ```

2.3 字符流

BufferedReader+InputStreamReader+FileInputStream

```Java FileInputStream fis = new FileInputStream("txt/InputStreamWriterTest.txt"); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr);

String str; while ((str = br.readLine()) != null) { System.out.println(str); } ```

BufferedWriter+OutputStreamWriter+FileOutputStream

```Java File file = new File("txt/OutputStreamWriterTest.txt"); FileOutputStream fos = new FileOutputStream(file); OutputStreamWriter osw = new OutputStreamWriter(fos); BufferedWriter bw = new BufferedWriter(osw);

bw.write("new bee"); bw.flush(); bw.close(); ```

使用上述方式就可以读写文件,但是过程过于繁琐,可以使用FileReader/FileWriter来简化流程。

BufferedReader+FileReader/BufferedWriter+FileWriter

```Java File srcFile = new File("txt/BufferedReaderTest.txt"); File dstFile = new File("txt/BufferedWriterTest.txt");

FileReader fr = new FileReader(srcFile); BufferedReader br = new BufferedReader(fr); FileWriter fw = new FileWriter(dstFile); BufferedWriter bw = new BufferedWriter(fw);

char[] str = new char[1024];

while ((br.read(str)) != -1) { bw.write(str); }

br.close(); bw.flush(); bw.close(); ```

2.4 InputStream-Reader

2.5 OutputStream-Writer

3 非流式部分

3.1 RandomAccessFile

特点

1、既可以读也可以写。

RandomAccessFile不属于InputStreamOutputStream类系的它是一个完全独立的类,所有方法(绝大多数都只属于它自己)都是自己从头开始规定的,这里面包含读写两种操作。

2、可以指定位置读写。

RandomAccessFile能在文件里面前后移动,在文件里移动用的seek(),所以它的行为与其它的I/O类 有些根本性的不同。总而言之,它是一个直接继承Object的,独立的类。只有RandomAccessFile才有seek方法,而这个方法也只适用于文件。

功能

可以用于多线程分段下载,断点续传。

```Java RandomAccessFile raf = new RandomAccessFile(file, "rw");

raf.seek(10); raf.setLength(1000);

raf.writeBoolean(true); raf.writeChar('a'); ```

RandomAccessFile(File file, String mode)

参数 mode 的值可选 "r":可读,"w" :可写,"rw":可读性。

seek(int index):可以将指针移动到某个位置开始读写。

setLength(long len):给写入文件预留空间。

3.2 NIO-FileChannel

Channel是对I/O操作的封装。

FileChannel配合着ByteBuffer,将读写的数据缓存到内存中,然后以批量/缓 存的方式read/write,省去了非批量操作时的重复中间操作,操纵大文件时可 以显著提高效率。

```Java package site.exciter.learn.io;

import android.os.Build;

import androidx.annotation.RequiresApi;

import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.time.Duration; import java.time.Instant;

public class FileChannelTest { @RequiresApi(api = Build.VERSION_CODES.O) public static void main(String[] args) { File sourceFile = new File("/Users/exciter/Desktop/test-01.zip"); File targetFile = new File("/Users/exciter/Desktop/test-02.zip"); targetFile.deleteOnExit(); try { targetFile.createNewFile(); } catch (Exception e) { e.printStackTrace(); }

    copyFileByFileChannel(sourceFile, targetFile);
}

@RequiresApi(api = Build.VERSION_CODES.O)
private static void copyFileByFileChannel(File sourceFile, File targetFile) {
    Instant begin = Instant.now();

    RandomAccessFile sourceRaf = null;
    RandomAccessFile targetRaf = null;

    try {
        sourceRaf = new RandomAccessFile(sourceFile, "r");
        targetRaf = new RandomAccessFile(targetFile, "rw");
    } catch (Exception e) {
        e.printStackTrace();
    }

    FileChannel sourceFileChannel = sourceRaf.getChannel();
    FileChannel targetFileChannel = targetRaf.getChannel();

    ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);

    try {
        while (sourceFileChannel.read(byteBuffer) != -1) {
            byteBuffer.flip();
            targetFileChannel.write(byteBuffer);
            byteBuffer.clear();
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            sourceFileChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            targetFileChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    System.out.println("time:" + Duration.between(begin, Instant.now()).toMillis());
}

} ```

关注木水小站 (zhangmushui.cn)和微信公众号【木水Code】,及时获取更多最新技术干货。