(最近剛來到簡書平臺,以前在CSDN上寫的一些東西,也在逐漸的移到這兒來怨绣,有些篇幅是很早的時候?qū)懴碌模虼丝赡軙吹揭恍﹥?nèi)容雜亂的文章拷获,對此深感抱歉篮撑,以下為正文)
正文
本篇要學(xué)習(xí)的是Java IO包中的FileInputStream類和FileOoutputStream類。
文件是我們常見的數(shù)據(jù)源之一匆瓜,所以Java為我們封裝好了支持文件讀寫的流工具赢笨,下面我們通過源碼分別來學(xué)習(xí)這兩個類。
FileInputStream.java
package java.io;
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
public class FileInputStream extends InputStream
{
//內(nèi)部聲明了一個FileDescriptor對象的句柄茧妒,用于接收被打的文件的句柄。
private final FileDescriptor fd;
//內(nèi)部聲明了一個String類型的變量path糠馆,用于存放被打開文件的文件路徑嘶伟。
private final String path;
//聲明了一個FileChannel對象,初始化為null又碌。該對象后面在NIO的學(xué)習(xí)中會詳細(xì)的提到九昧,可以對文件進(jìn)行操作
private FileChannel channel = null;
//定義了一個Object對象,為后面進(jìn)行鎖操作時提供對象鎖毕匀。
private final Object closeLock = new Object();
//定義了一個booelan型變量铸鹰,closed,來判斷流是否關(guān)閉皂岔,注意的是該變量被volatile關(guān)鍵字修飾蹋笼,保證了數(shù)據(jù)改變時的可見性。
private volatile boolean closed = false;
/**
* 一個帶一個參數(shù)的構(gòu)造函數(shù),傳入的參數(shù)為所要打開文件的文件路徑剖毯。創(chuàng)建時進(jìn)行安全監(jiān)測圾笨,檢測傳入?yún)?shù)是否為null,然后調(diào)用下一個構(gòu)造函數(shù)逊谋。
*/
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
/**
* 一個帶一個參數(shù)的構(gòu)造函數(shù)擂达,傳入的參數(shù)為所要打開文件的file對象。創(chuàng)建是進(jìn)行安全監(jiān)測胶滋,檢測file對象是否為null板鬓。
*/
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
//獲取當(dāng)前應(yīng)用的安全管理器SecruityManager,并對其是否有讀取權(quán)限進(jìn)行檢測究恤。
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
//如果傳入文件的路徑為null俭令,拋出NullPointerException。
if (name == null) {
throw new NullPointerException();
}
//判斷文件是否合法
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
//創(chuàng)建一個文件描述符部宿,并將本類對象依附到該文件描述符上抄腔,并將全局變量path賦值為傳入文件的路徑,最終通過一個native的open方法打開文件窟赏。這里的attach
//方法目的時方便最后的close方法調(diào)用時妓柜,可以方便的關(guān)閉所有需要關(guān)閉的資源箱季,這就是我們使用多個包裝流的時候無需一個一個的關(guān)閉所有流的原因涯穷。
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
/**
* 一個帶有一個參數(shù)的構(gòu)造方法,傳入的參數(shù)為一個文件描述符藏雏。開始時需要對傳入?yún)?shù)進(jìn)行安全監(jiān)測拷况,如果為null,則拋出NullPointerException掘殴。
*/
public FileInputStream(FileDescriptor fdObj) {
//獲得java的安全管理器赚瘦。
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
}
//如果成功獲得了安全管理器,則對傳入的文件描述符進(jìn)行權(quán)限檢測奏寨,是否具有讀數(shù)據(jù)的權(quán)限起意。
if (security != null) {
security.checkRead(fdObj);
}
//將傳入的文件描述符fdObj賦值給全局變量fd,全局變量path賦值為null病瞳,將本類對象依附到文件描述符之上揽咕。
fd = fdObj;
path = null;
fd.attach(this);
}
/**
* 一個native關(guān)鍵字修飾的open方法,含有一個String類型的參數(shù)套菜,傳入的參數(shù)為所需打開文件的路徑亲善。關(guān)于native方法因?yàn)闋砍兜絚/c++,以后再開篇幅具體描述逗柴。
* 此時只需知道該方法用于打開一個文件蛹头。
*/
private native void open(String name) throws FileNotFoundException;
/**
* read方法,實(shí)際調(diào)用native方法read0()來實(shí)際讀取文件。
*/
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;
private native int readBytes(byte b[], int off, int len) throws IOException;
/**
* 一個帶參的read方法渣蜗,傳入的參數(shù)為一個byte型數(shù)組屠尊,最終調(diào)用native方法readBytes方法
*/
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}
/**
* 一個native關(guān)鍵字修飾的skip方法,傳入的參數(shù)為一個long型數(shù)據(jù)耕拷,方法用于跳過傳入的長度進(jìn)行閱讀知染。
*/
public native long skip(long n) throws IOException;
/**
* 該方法返回一個預(yù)估的流中還可以讀取的數(shù)據(jù)長度
*/
public native int available() throws IOException;
/**
* 用于關(guān)閉流。
*/
public void close() throws IOException {
//加了一個同步鎖斑胜,用來設(shè)置closed狀態(tài)值控淡。
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
//如果文件管道不為null,調(diào)用相應(yīng)的close方法止潘。
if (channel != null) {
channel.close();
}
//調(diào)用文件描述符的closeAll方法掺炭,會將所有依附的系統(tǒng)資源全部釋放。
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
/**
* 獲得當(dāng)前流中讀取文件的文件描述符
*/
public final FileDescriptor getFD() throws IOException {
if (fd != null) {
return fd;
}
throw new IOException();
}
/**
* 獲取當(dāng)前流中所要讀取文件的文件管道凭戴。
*/
public FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = FileChannelImpl.open(fd, path, true, false, this);
}
return channel;
}
}
// 用于設(shè)置fd的內(nèi)存地址偏移量涧狮,牽扯到JNI編程,這里不細(xì)說么夫。
private static native void initIDs();
private native void close0() throws IOException;
static {
initIDs();
}
/**
* 重寫了finalize方法者冤,用于再一次確定資源的關(guān)閉。需要注意的是當(dāng)fd被分享出去后档痪,必須等到所有使用到fd引用的資源都不可達(dá)時涉枫,調(diào)用close方法。
*/
protected void finalize() throws IOException {
if ((fd != null) && (fd != FileDescriptor.in)) {
close();
}
}
}
FileOutput.java
package java.io;
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
public class FileOutputStream extends OutputStream
{
/**
* 內(nèi)置了一個文件描述符的句柄腐螟,用于接收打開文件的文件描述符對象愿汰。
*/
private final FileDescriptor fd;
/**
* 聲明了一個boolean類型的變量append,該變量值用于控制打開文件寫入時是以追加的形式寫入數(shù)據(jù)還是以覆蓋的形式寫入數(shù)據(jù)乐纸。
*/
private final boolean append;
/**
* 內(nèi)置了一個文件管道衬廷,用于接收打開文件的文件管道對象。它的初始化不是最初就初始化的汽绢,只有在需要時(調(diào)用getChannel方法時)才會進(jìn)行初始化吗跋。
*/
private FileChannel channel;
/**
* 聲明了一個String類型的變量,用于接收打開文件的路徑名宁昭。
*/
private final String path;
//定義了一個Object對象跌宛,為后面的同步操作提供同步鎖。
private final Object closeLock = new Object();
//定義了一個boolean型變量closed久窟,用來控制當(dāng)前流對象是否關(guān)閉秩冈。用volatile關(guān)鍵修飾后,保證了數(shù)據(jù)改變時的可見性斥扛。
private volatile boolean closed = false;
/**
* 一個帶有一個參數(shù)的構(gòu)造函數(shù)入问,傳入的參數(shù)類型為String類型丹锹,為要寫入文件的路徑,內(nèi)部調(diào)用了下面的帶兩個參數(shù)的構(gòu)造方法芬失,默認(rèn)寫人模式是覆蓋的楣黍。
*/
public FileOutputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null, false);
}
/**
* 一個帶有兩個參數(shù)的構(gòu)造函數(shù),第一個參數(shù)為String類型棱烂,為要寫入文件的路徑租漂,第二個參數(shù)為一個boolean型變量,用于控制文件寫入時的方式颊糜。內(nèi)部調(diào)用另一個構(gòu)造
* 函數(shù)哩治。
*/
public FileOutputStream(String name, boolean append)
throws FileNotFoundException
{
this(name != null ? new File(name) : null, append);
}
/**
* 一個帶有一個參數(shù)的構(gòu)造函數(shù),傳入的參數(shù)類型為一個File對象衬鱼,內(nèi)部繼續(xù)調(diào)用其它的構(gòu)造函數(shù)业筏,默認(rèn)寫入方式是覆蓋的。
*/
public FileOutputStream(File file) throws FileNotFoundException {
this(file, false);
}
/**
* 帶兩個參數(shù)的構(gòu)造函數(shù)鸟赫,其它的構(gòu)造函數(shù)最終都是調(diào)用該構(gòu)造函數(shù)蒜胖,第一個參數(shù)為一個File類型,表示要打開的文件抛蚤,第二個參數(shù)是一個boolean型變量台谢,用于控制寫
* 入時是覆蓋還是追加。
*/
public FileOutputStream(File file, boolean append)
throws FileNotFoundException
{
//獲取打開文件的路徑岁经。
String name = (file != null ? file.getPath() : null);
//獲取java安全管理器朋沮,對當(dāng)前是否具有寫入的權(quán)限進(jìn)行檢查。
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkWrite(name);
}
//檢測是否成功獲得文件路徑名蒿偎。
if (name == null) {
throw new NullPointerException();
}
//檢測傳入的文件是否合法朽们。
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
//將該流與文件描述符相依附,為后面進(jìn)行close時提供便捷诉位。
this.fd = new FileDescriptor();
fd.attach(this);
this.append = append;
this.path = name;
//調(diào)用native方法open,打開需要就行寫入的文件菜枷。
open(name, append);
}
/**
* 一個帶一個參數(shù)的構(gòu)造函數(shù)苍糠,參數(shù)類型是文件描述符類型,具體過程同上啤誊,此處不再細(xì)說岳瞭。
*/
public FileOutputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
}
if (security != null) {
security.checkWrite(fdObj);
}
this.fd = fdObj;
this.append = false;
this.path = null;
fd.attach(this);
}
/**
* 用于打開指定文件,第一個String類型參數(shù)為需要打開文件的路徑蚊锹,第二個boolean類型參數(shù)為打開模式瞳筏,是追加還是覆蓋。
*/
private native void open(String name, boolean append)
throws FileNotFoundException;
/**
* 用于向打開文件中寫入數(shù)據(jù)牡昆,第一個參數(shù)是一個int型變量姚炕,為要寫入的數(shù)據(jù),第二個boolean型參數(shù)為寫入時是追加還是覆蓋。
*/
private native void write(int b, boolean append) throws IOException;
/**
* 向文件中寫入數(shù)據(jù)柱宦,內(nèi)部調(diào)用上面的native方法進(jìn)行寫入些椒。
*/
public void write(int b) throws IOException {
write(b, append);
}
/**
* 向文件中寫入數(shù)據(jù),不過不是一次寫入一個字節(jié)掸刊,而是通過一個byte數(shù)組作為緩存免糕,一次寫入一批數(shù)據(jù)。
*/
private native void writeBytes(byte b[], int off, int len, boolean append)
throws IOException;
/**
* 向文件中寫入數(shù)據(jù)忧侧,將傳入的byte數(shù)組中的內(nèi)容全部寫入至文件中石窑。
*/
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length, append);
}
/**
* 向文件中寫入數(shù)據(jù),傳入的三個參數(shù)分別是寫入的數(shù)據(jù)源一個byte數(shù)組蚓炬,后兩個參數(shù)為寫入數(shù)據(jù)的起點(diǎn)以及寫入數(shù)據(jù)的長度尼斧。
*/
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len, append);
}
/**
* 該方法用于關(guān)閉流及與其相關(guān)聯(lián)的所有的系統(tǒng)資源。
*/
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
channel.close();
}
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
/**
* 獲得打開文件的文件描述符试吁。
*/
public final FileDescriptor getFD() throws IOException {
if (fd != null) {
return fd;
}
throw new IOException();
}
/**
* 獲得打開文件的文件管道棺棵。
*/
public FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = FileChannelImpl.open(fd, path, false, true, append, this);
}
return channel;
}
}
/**
* 重寫了finalize方法,調(diào)用close方法保證流與其關(guān)聯(lián)的資源能夠獲得釋放熄捍。如果是標(biāo)準(zhǔn)輸出流或者錯誤輸出流烛恤,還會調(diào)用flush方法。
*/
protected void finalize() throws IOException {
if (fd != null) {
if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
flush();
} else {
close();
}
}
}
private native void close0() throws IOException;
private static native void initIDs();
static {
initIDs();
}
}
上面附上了兩個類的源碼余耽,并附上了一些簡單的注釋缚柏。下面我們就用具體的例子來加深我們對這兩個類的理解。
下面的例子實(shí)現(xiàn)了簡單的文件復(fù)制:
package FileInputOutput;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileIOTest {
public static void main(String[] args) {
byte[] buffer = new byte[1024];
try (FileInputStream fis = new FileInputStream("./src/file/test.txt");
FileOutputStream fos = new FileOutputStream(
"./src/file/testcopy.txt")) {
while (fis.read(buffer) != -1) {
fos.write(buffer);
}
System.out.println("復(fù)制完成");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
執(zhí)行上述代碼后可以看到在指定目錄下復(fù)制了一份test.txt文件碟贾,文件中內(nèi)容也被完整復(fù)制過來币喧。
執(zhí)行效果
以上為本篇的全部內(nèi)容。