本文內容:IO流操作文件的細節分析;分析各種操作文件的方式。
讀寫一個文件
從一個示例開始分析,如何操作文件:
/**
* 向一個文件中寫入數據
* @throws IOException
*/
private static void writeFile() throws IOException {
File file = new File("D://tmp/a.txt");
try (OutputStream outputStream = new FileOutputStream(file, false)) {
outputStream.write("ABC".getBytes(StandardCharsets.UTF_8));
}
}
/**
* 從一個文件中讀取數據
* @throws IOException
*/
private static void readFile() throws IOException {
File file = new File("D://tmp/a.txt");
try (FileInputStream inputStream = new FileInputStream(file)) {
StringBuilder stringBuffer = new StringBuilder();
int count;
while ((count = inputStream.read()) != -1) {
stringBuffer.append((char) count);
}
System.out.println(stringBuffer.toString());
}
}
文件寫入
向一個文件寫入數據時,首先新建一個文件,然后定義一個輸出流,為什么數輸出流?Java里的API都是面向JVM的,輸入和輸出也是面向jvm的,輸入就是向jvm中輸入,輸出就是從jvm中輸出;
outputStream.write() 方法將一個字節數組寫入到a.txt中,執行完該方法后文件中就有內容了。程序是如何寫進去的?
/**
* Opens a file, with the specified name, for overwriting or appending.
* @param name name of file to be opened
* @param append whether the file is to be opened in append mode
*/
private native void open0(String name, boolean append)
throws FileNotFoundException;
/**
* Writes the specified byte to this file output stream.
*
* @param b the byte to be written.
* @param append {@code true} if the write operation first
* advances the position to the end of file
*/
private native void write(int b, boolean append) throws IOException;
/**
* Writes a sub array as a sequence of bytes.
* @param b the data to be written
* @param off the start offset in the data
* @param len the number of bytes that are written
* @param append {@code true} to first advance the position to the
* end of file
* @exception IOException If an I/O error has occurred.
*/
private native void writeBytes(byte b[], int off, int len, boolean append)
throws IOException;
private native void close0() throws IOException;
從源碼上看,文件寫入需要三個步驟:1、打開一個文件,也就是獲取文件的句柄;2、向文件中輸出字節流;3、關閉輸出流,釋放資源;
open0(String name, boolean append) 這個本地方法就是打開文件的方法,調用了操作系統的API。
write(int b, boolean append)這個本地方法是最終的寫入的方法,參數有兩個:b[],append; b代表的是一個字節數組,append代表是否添加,false則會覆蓋初始文件。
文件讀取
與寫入文件類似,文件讀取同樣也是相對與jvm,文件讀取是向jvm中輸入,所以是InputStream;
private native int read0() throws IOException;
/**
* Reads a subarray as a sequence of bytes.
* @param b the data to be written
* @param off the start offset in the data
* @param len the number of bytes that are written
* @exception IOException If an I/O error has occurred.
*/
private native int readBytes(byte b[], int off, int len) throws IOException;
讀取文件也是兩種方法,一個字節一個字節地讀,也能安裝字節數組去讀。步驟與文件寫入類似:1、打開一個文件,也就是獲取文件的句柄;2、從文件中讀取字節流;3、關閉輸入流,釋放資源;
細節分析
字節數組讀寫的問題
按照字節寫入,是直接調用native方法獲取文件句柄操作文件,並且是直接寫入字節數組,但是如果是寫入中文,就不一樣了,UTF-8編碼中漢字是3個字節,英文字母是1個字節;當然不同的編碼方式還不一樣。這樣寫入不會有問題,但是讀取的時候,問題就來了,漢字是3個字節,不管是按照字節數組還是字節去讀,都可能只讀取了一個漢字的一半,這樣讀取出來的字節數組轉成可視化的內容就會出現亂碼。
如果是從一個輸入流到另外一個輸出流,比如文件導出,就可以使用輸入流讀取的字節數組直接放到輸出流中去。注意:讀取到文件最后會返回-1,可以以此為分界點。
private static void writeFile0() throws IOException {
File fileA = new File("D://tmp/a.txt");
File fileB = new File("D://tmp/b.txt");
try (FileInputStream inputStream = new FileInputStream(fileA);
OutputStream outputStream = new FileOutputStream(fileB)) {
int count;
byte[] bytes = new byte[64];
while ((count = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, count);
}
}
}
按照字符讀取
在Java API中提供了另外一種方式操作文件,那就是按照字符讀取,也能按行讀取。這種讀取方式在文件和jvm中間加了一層緩沖區。
private static void readFile1() throws IOException {
File file = new File("D://tmp/a.txt");
try (FileReader fileReader = new FileReader(file)) {
BufferedReader bufferedReader = new BufferedReader(fileReader);
int count;
StringBuilder stringBuilder = new StringBuilder();
while ((count = bufferedReader.read()) != -1) {
// 還可以按行讀取bufferedReader.readLine();
stringBuilder.append((char) count);
}
System.out.println(stringBuilder.toString());
}
}