Java File IO 复杂度

读写方式

Java中的File IO基本可以分为三类:

  • 面向字节传输的传统IO方式
  • FileChannel文件通道
  • mmap内存映射

Buffered Stream I/O

BufferedReader通过内部buffer将文件的一部分字节缓存下来,减少频繁的磁盘IO。

public static void testStream(){
        try (BufferedReader reader = Files.newBufferedReader(Paths.get(PATH), StandardCharsets.UTF_8)) {
            String line = null;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException x) {
            System.err.format("IOException: %s%n", x);
        }
    }

Unbuffered Streams I/O

public static void testUnStream() {
        try (InputStream in = Files.newInputStream(Paths.get(PATH));
             BufferedReader reader =
                     new BufferedReader(new InputStreamReader(in))) {
            String line = null;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException x) {
            System.err.println(x);
        }
    }

Channel I/O

public static byte[] readAllBytes(Path path) throws IOException {
        try (SeekableByteChannel sbc = Files.newByteChannel(path);
             InputStream in = Channels.newInputStream(sbc)) {
            long size = sbc.size();
            if (size > (long)MAX_BUFFER_SIZE)
                throw new OutOfMemoryError("Required array size too large");

            return read(in, (int)size);
        }
    }


public static SeekableByteChannel newByteChannel(Path path, OpenOption... options)
        throws IOException
    {
        Set<OpenOption> set = new HashSet<OpenOption>(options.length);
        Collections.addAll(set, options);
        return newByteChannel(path, set);
    }


public static SeekableByteChannel newByteChannel(Path path,
                                                     Set<? extends OpenOption> options,
                                                     FileAttribute<?>... attrs)
        throws IOException
    {
        return provider(path).newByteChannel(path, options, attrs);
    }

public abstract SeekableByteChannel newByteChannel(Path path,
        Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException;
public static void testChannel() {
        try (SeekableByteChannel sbc = Files.newByteChannel(Paths.get(PATH))) {
            final int BUFFER_CAPACITY = 10;
            ByteBuffer buf = ByteBuffer.allocate(BUFFER_CAPACITY);

            // Read the bytes with the proper encoding for this platform. If
            // you skip this step, you might see foreign or illegible
            // characters.
            String encoding = System.getProperty("file.encoding");
            while (sbc.read(buf) > 0) {
                buf.flip();
                System.out.print(Charset.forName(encoding).decode(buf));
                buf.clear();
            }
        }catch (IOException e){
            System.err.println(e);
        }
    }

FileChannel

public static void testFileChannel() {
        try {
            FileChannel fileChannel = new RandomAccessFile(new File(PATH), "rw").getChannel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(4);
            fileChannel.read(byteBuffer);
            String str = new String(byteBuffer.array(), StandardCharsets.UTF_8);
            System.out.println(str);
        }catch (IOException e){
            System.err.println(e);
        }
    }

mmap

适用于读写小文件的应用场景。

public static void testMmap() {
        try {
            FileChannel fileChannel = new RandomAccessFile(new File(PATH), "rw").getChannel();
            MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size());
            byte[] data = new byte[4];
            mappedByteBuffer.get(data);
            String str = new String(data, StandardCharsets.UTF_8);
            System.out.println(str);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

使用mmap方式对的几个缺点:

  1. 适用于文件大小确定的使用场景,因为使用前必须指定好内存映射的大小,一次map的大小限制在1.5G左右,重复map存在虚拟内存的多次分配回收。
  2. mmap内存回收的方式繁琐。

Reference

https://docs.oracle.com/javase/tutorial/essential/io/file.html

https://docs.oracle.com/javase/tutorial/essential/io/rafs.html