<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Java IO/NIO多種讀寫文件方式

    上一章節我們提到了Java 對文件的讀寫分為了基于阻塞模式的IO和非阻塞模式的NIO,本章節我將列舉一些我們常用于讀寫文件的方式。

    我們通常讀寫文件都是使用的阻塞模式,與之對應的也就是java.io.FileSystemjava.io.FileInputStream類提供了對文件的讀取功能,Java的其他讀取文件的方法基本上都是封裝了java.io.FileInputStream類,比如:java.io.FileReader

    FileInputStream

    使用FileInputStream實現文件讀取Demo:

    package com.anbai.sec.filesystem;
    
    import java.io.*;
    
    /**
     * Creator: yz
     * Date: 2019/12/4
     */
    public class FileInputStreamDemo {
    
        public static void main(String[] args) throws IOException {
            File file = new File("/etc/passwd");
    
            // 打開文件對象并創建文件輸入流
            FileInputStream fis = new FileInputStream(file);
    
            // 定義每次輸入流讀取到的字節數對象
            int a = 0;
    
            // 定義緩沖區大小
            byte[] bytes = new byte[1024];
    
            // 創建二進制輸出流對象
            ByteArrayOutputStream out = new ByteArrayOutputStream();
    
            // 循環讀取文件內容
            while ((a = fis.read(bytes)) != -1) {
                // 截取緩沖區數組中的內容,(bytes, 0, a)其中的0表示從bytes數組的
                // 下標0開始截取,a表示輸入流read到的字節數。
                out.write(bytes, 0, a);
            }
    
            System.out.println(out.toString());
        }
    
    }
    

    輸出結果如下:

    ##
    # User Database
    # 
    # Note that this file is consulted directly only when the system is running
    # in single-user mode.  At other times this information is provided by
    # Open Directory.
    #
    # See the opendirectoryd(8) man page for additional information about
    # Open Directory.
    ##
    nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
    root:*:0:0:System Administrator:/var/root:/bin/sh
    daemon:*:1:1:System Services:/var/root:/usr/bin/false
    .....內容過長省去多余內容
    

    調用鏈如下:

    java.io.FileInputStream.readBytes(FileInputStream.java:219)
    java.io.FileInputStream.read(FileInputStream.java:233)
    com.anbai.sec.filesystem.FileInputStreamDemo.main(FileInputStreamDemo.java:27)
    

    其中的readBytes是native方法,文件的打開、關閉等方法也都是native方法:

    private native int readBytes(byte b[], int off, int len) throws IOException;
    private native void open0(String name) throws FileNotFoundException;
    private native int read0() throws IOException;
    private native long skip0(long n) throws IOException;
    private native int available0() throws IOException;
    private native void close0() throws IOException;
    

    java.io.FileInputStream類對應的native實現如下:

    JNIEXPORT void JNICALL
    Java_java_io_FileInputStream_open0(JNIEnv *env, jobject this, jstring path) {
        fileOpen(env, this, path, fis_fd, O_RDONLY);
    }
    
    JNIEXPORT jint JNICALL
    Java_java_io_FileInputStream_read0(JNIEnv *env, jobject this) {
        return readSingle(env, this, fis_fd);
    }
    
    JNIEXPORT jint JNICALL
    Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,
            jbyteArray bytes, jint off, jint len) {
        return readBytes(env, this, bytes, off, len, fis_fd);
    }
    
    JNIEXPORT jlong JNICALL
    Java_java_io_FileInputStream_skip0(JNIEnv *env, jobject this, jlong toSkip) {
        jlong cur = jlong_zero;
        jlong end = jlong_zero;
        FD fd = GET_FD(this, fis_fd);
        if (fd == -1) {
            JNU_ThrowIOException (env, "Stream Closed");
            return 0;
        }
        if ((cur = IO_Lseek(fd, (jlong)0, (jint)SEEK_CUR)) == -1) {
            JNU_ThrowIOExceptionWithLastError(env, "Seek error");
        } else if ((end = IO_Lseek(fd, toSkip, (jint)SEEK_CUR)) == -1) {
            JNU_ThrowIOExceptionWithLastError(env, "Seek error");
        }
        return (end - cur);
    }
    
    JNIEXPORT jint JNICALL
    Java_java_io_FileInputStream_available0(JNIEnv *env, jobject this) {
        jlong ret;
        FD fd = GET_FD(this, fis_fd);
        if (fd == -1) {
            JNU_ThrowIOException (env, "Stream Closed");
            return 0;
        }
        if (IO_Available(fd, &ret)) {
            if (ret > INT_MAX) {
                ret = (jlong) INT_MAX;
            } else if (ret < 0) {
                ret = 0;
            }
            return jlong_to_jint(ret);
        }
        JNU_ThrowIOExceptionWithLastError(env, NULL);
        return 0;
    }
    

    完整代碼參考OpenJDK:openjdk/src/java.base/share/native/libjava/FileInputStream.c

    FileOutputStream

    使用FileOutputStream實現寫文件Demo:

    package com.anbai.sec.filesystem;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    /**
     * Creator: yz
     * Date: 2019/12/4
     */
    public class FileOutputStreamDemo {
    
        public static void main(String[] args) throws IOException {
            // 定義寫入文件路徑
            File file = new File("/tmp/1.txt");
    
            // 定義待寫入文件內容
            String content = "Hello World.";
    
            // 創建FileOutputStream對象
            FileOutputStream fos = new FileOutputStream(file);
    
            // 寫入內容二進制到文件
            fos.write(content.getBytes());
            fos.flush();
            fos.close();
        }
    
    }
    

    代碼邏輯比較簡單: 打開文件->寫內容->關閉文件,調用鏈和底層實現分析請參考FileInputStream

    RandomAccessFile

    Java提供了一個非常有趣的讀取文件內容的類: java.io.RandomAccessFile,這個類名字面意思是任意文件內容訪問,特別之處是這個類不僅可以像java.io.FileInputStream一樣讀取文件,而且還可以寫文件。

    RandomAccessFile讀取文件測試代碼:

    package com.anbai.sec.filesystem;
    
    import java.io.*;
    
    /**
     * Creator: yz
     * Date: 2019/12/4
     */
    public class RandomAccessFileDemo {
    
        public static void main(String[] args) {
            File file = new File("/etc/passwd");
    
            try {
                // 創建RandomAccessFile對象,r表示以只讀模式打開文件,一共有:r(只讀)、rw(讀寫)、
                // rws(讀寫內容同步)、rwd(讀寫內容或元數據同步)四種模式。
                RandomAccessFile raf = new RandomAccessFile(file, "r");
    
                // 定義每次輸入流讀取到的字節數對象
                int a = 0;
    
                // 定義緩沖區大小
                byte[] bytes = new byte[1024];
    
                // 創建二進制輸出流對象
                ByteArrayOutputStream out = new ByteArrayOutputStream();
    
                // 循環讀取文件內容
                while ((a = raf.read(bytes)) != -1) {
                    // 截取緩沖區數組中的內容,(bytes, 0, a)其中的0表示從bytes數組的
                    // 下標0開始截取,a表示輸入流read到的字節數。
                    out.write(bytes, 0, a);
                }
    
                System.out.println(out.toString());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    任意文件讀取特性體現在如下方法:

    // 獲取文件描述符
    public final FileDescriptor getFD() throws IOException 
    
    // 獲取文件指針
    public native long getFilePointer() throws IOException;
    
    // 設置文件偏移量
    private native void seek0(long pos) throws IOException;
    

    java.io.RandomAccessFile類中提供了幾十個readXXX方法用以讀取文件系統,最終都會調用到read0或者readBytes方法,我們只需要掌握如何利用RandomAccessFile讀/寫文件就行了。

    RandomAccessFile寫文件測試代碼:

    package com.anbai.sec.filesystem;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    
    /**
     * Creator: yz
     * Date: 2019/12/4
     */
    public class RandomAccessWriteFileDemo {
    
        public static void main(String[] args) {
            File file = new File("/tmp/test.txt");
    
            // 定義待寫入文件內容
            String content = "Hello World.";
    
            try {
                // 創建RandomAccessFile對象,rw表示以讀寫模式打開文件,一共有:r(只讀)、rw(讀寫)、
                // rws(讀寫內容同步)、rwd(讀寫內容或元數據同步)四種模式。
                RandomAccessFile raf = new RandomAccessFile(file, "rw");
    
                // 寫入內容二進制到文件
                raf.write(content.getBytes());
                raf.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    FileSystemProvider

    前面章節提到了JDK7新增的NIO.2的java.nio.file.spi.FileSystemProvider,利用FileSystemProvider我們可以利用支持異步的通道(Channel)模式讀取文件內容。

    FileSystemProvider讀取文件內容示例:

    package com.anbai.sec.filesystem;
    
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    /**
     * Creator: yz
     * Date: 2019/12/4
     */
    public class FilesDemo {
    
        public static void main(String[] args) {
            // 通過File對象定義讀取的文件路徑
    //        File file  = new File("/etc/passwd");
    //        Path path1 = file.toPath();
    
            // 定義讀取的文件路徑
            Path path = Paths.get("/etc/passwd");
    
            try {
                byte[] bytes = Files.readAllBytes(path);
                System.out.println(new String(bytes));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    java.nio.file.Files是JDK7開始提供的一個對文件讀寫取非常便捷的API,其底層實在是調用了java.nio.file.spi.FileSystemProvider來實現對文件的讀寫的。最為底層的實現類是sun.nio.ch.FileDispatcherImpl#read0

    基于NIO的文件讀取邏輯是:打開FileChannel->讀取Channel內容。

    打開FileChannel的調用鏈為:

    sun.nio.ch.FileChannelImpl.<init>(FileChannelImpl.java:89)
    sun.nio.ch.FileChannelImpl.open(FileChannelImpl.java:105)
    sun.nio.fs.UnixChannelFactory.newFileChannel(UnixChannelFactory.java:137)
    sun.nio.fs.UnixChannelFactory.newFileChannel(UnixChannelFactory.java:148)
    sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:212)
    java.nio.file.Files.newByteChannel(Files.java:361)
    java.nio.file.Files.newByteChannel(Files.java:407)
    java.nio.file.Files.readAllBytes(Files.java:3152)
    com.anbai.sec.filesystem.FilesDemo.main(FilesDemo.java:23)
    

    文件讀取的調用鏈為:

    sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:147)
    sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:65)
    sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:109)
    sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:103)
    java.nio.file.Files.read(Files.java:3105)
    java.nio.file.Files.readAllBytes(Files.java:3158)
    com.anbai.sec.filesystem.FilesDemo.main(FilesDemo.java:23)
    

    FileSystemProvider寫文件示例:

    package com.anbai.sec.filesystem;
    
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    /**
     * Creator: yz
     * Date: 2019/12/4
     */
    public class FilesWriteDemo {
    
        public static void main(String[] args) {
            // 通過File對象定義讀取的文件路徑
    //        File file  = new File("/etc/passwd");
    //        Path path1 = file.toPath();
    
            // 定義讀取的文件路徑
            Path path = Paths.get("/tmp/test.txt");
    
            // 定義待寫入文件內容
            String content = "Hello World.";
    
            try {
                // 寫入內容二進制到文件
                Files.write(path, content.getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    文件讀寫總結

    Java內置的文件讀取方式大概就是這三種方式,其他的文件讀取API可以說都是對這幾種方式的封裝而已(依賴數據庫、命令執行、自寫JNI接口不算,本人個人理解,如有其他途徑還請告知)。本章我們通過深入基于IO和NIO的Java文件系統底層API,希望大家能夠通過以上Demo深入了解到文件讀寫的原理和本質。

    本文章首發在 網安wangan.com 網站上。

    上一篇 下一篇
    討論數量: 0
    只看當前版本


    暫無話題~
    亚洲 欧美 自拍 唯美 另类