Java IO之FileInputStream與FileOutputStream對象常用操作
涉及到文件(非文件夾)內容的操作,除了要用到File(見之前文章),另外就必須用到輸入流或輸出流。
輸入流:該流處理時,數據由外部流向程序(內存),一般指代“讀取數據”,更清晰點地說:從外部讀取數據到內存中。
輸出流:該流處理時,數據由程序(內存)流向外部,一般指代“寫入數據”,更清晰點地說:將數據從內存寫入到外部。
如果要操作字節(比如Soket,字節數據、圖片、視屏等非純文本文件),則采用字節輸入流與字節輸出流。在Java中,可使用: InputStream 與 OutputStream 及其子類。
如果要操作字符(比如字符數據、純文本文件),則采用字符輸入流與字符輸出流。在Java中,可使用:Reader 與 Writer 及其子類。
對字節的操作,采用 InputStream與OutputStream。它們的為聲明分別為:
public abstract class InputStream implements Closeable
public abstract class OutputStream implements Closeable, Flushable
它們都是抽象類,需要由具體子類進行實例化。
InputStream主要子類有:
- AudioInputStream:讀取音像媒體的字節流。
- ByteArrayInputStream:讀取字節數組的字節流。
- FileInputStream:讀取文件的字節流。
- FilterInputStream:過濾器輸入流,用於裝飾。其子類有:PushbackInputStream , BufferedInputStream
- ObjectInputStream:讀取序列化的對象的字節流。
- PipedInputStream:讀取管道的字節流。
- SequenceInputStream:讀取序列的字節流。
- PushbackInputStream:讀取可以推回的字節流。繼承自:FilterInputStream
- BufferedInputStream:讀取有緩存效果的字節流。繼承自:FilterInputStream
- StringBufferInputStream:讀取字符緩沖的字節流。已過時,不作介紹。
常用的有FileInputStream,我們將以它為例。
OutputStream主要子類有:
- ByteArrayOutputStream:將字節寫入到字節數組
- FileOutputStream:將字節寫入到文件
- FilterOutputStream:過濾器輸出流,用於裝飾
- ObjectOutputStream:將字節寫入到序列化對象
- PipedOutputStream:將字節寫入管道
常用的有FileOutputStream,我們將以它為例。
InputStream與OutputStream的方法
InputStream字節輸入流,其主要方法有:
int available():獲取可用的字節總長度。返回int類型,該方法只要用於獲取可被后續線程使用的流的長度,並且隨已讀取字節的變化而變化(見下邊例子),特別是用它來表示文件大小時並不可靠(因為特大文件的字節長度可能是long字節。對於文件字節數,可以使用file.length()來代替)。
void close():釋放流對象。用於關閉資源。close()后無法進行read及skip等操作。所以一般在流操作結束后進行close()調用。
void mark(int readlimit):標記流的位置。系統不一定支持。不推薦使用。
boolean markSupported():檢測是否支持流位置標記。(mark方法與reset方法在其支持下才可用。一般能不用則不用此3個方法)
abstract int read():從流中讀取一個字節,如果沒有數據,返回-1。
int read(byte[] b):從流中讀取字節,並將數據存入到指定的字節數組中,讀取的字節數為指定數組的長度。如果沒有數據,返回-1。
int read(byte[] b, int off, int len):從流中讀取字節,並將數據存入到指定的字節數組中,讀取的字節數為len,存入時從數組off開始存。如果沒有數據,返回-1。
void reset():從新設置流的開始。系統不一定支持。不推薦使用。
long skip(long n):跳過指定數目字節。可以是正數負數或0(對於文件流來說,該方法已被重載,可以用來解決系統不支持reset()問題。負數是往回轉跳,0不進行轉跳。)。
輸入字節流InputStream用於“讀取”,其中最常用的方法是:read(), read(byte[] b), read(byte[] b, int off, int len)
綜合測試代碼:
1 import java.io.File; 2 import java.io.FileInputStream; 3 import java.io.IOException; 4 import java.io.InputStream; 5 6 public class File001 { 7 public static void main(String[] args) throws IOException { 8 File file = new File("g:/java2019/file.txt");//文件內容:abc 9 10 //打開流 11 InputStream is = new FileInputStream(file); 12 long len = file.length(); 13 System.out.println("available:"+is.available()); 14 System.out.println(is.read());//讀取第一個字節:a 15 System.out.println(is.read());//讀取第一個字節:b 16 System.out.println(is.read());//讀取第一個字節:c 17 System.out.println(is.read());//-1 18 System.out.println(is.read());//-1 19 System.out.println(is.read());//-1 20 21 System.out.println("available:"+is.available()); 22 23 if(is.markSupported()){ 24 System.out.println("IO支持標記,可以進行重定位到開頭標記處。"); 25 is.reset(); 26 }else{ 27 System.out.println("IO不支持標記,不可以進行重定位到開頭標記處。使用回跳處理。"); 28 is.skip(-len); 29 } 30 31 for(int i=0;(i=is.read())!=-1;){ 32 System.out.println(i); 33 } 34 35 is.skip(-len); 36 37 byte[] b3 = new byte[3]; 38 int len2 = is.read(b3,0,1);//從流中讀取1個字節,存在b3字節數組中,從b3的0位置開始存。 39 System.out.println("讀取長度為::"+len2); 40 System.out.println("first byte:"+b3[0]); 41 42 is.skip(-1); 43 is.read(b3);//從流中讀取b3字節數組對應大小的字節存入b3中。 44 for(byte b : b3){ 45 System.out.println(b); 46 } 47 48 //關閉流 49 is.close(); 50 } 51 52 }
輸出:
available:3
97
98
99
-1
-1
-1
available:0
IO不支持標記,不可以進行重定位到開頭標記處。使用回跳處理。
97
98
99
讀取長度為::1
first byte:97
97
98
99
說明:
- FileInputStream是文件字節輸入流。其構造方法有:FileInputStream(File file) ,FileInputStream(String name), FileInputStream(FileDescriptor fdObj)。其中FileInputStream(String name)最為常用,它其實調用的也是FileInputStream(File file)。如果沒有什么獲取文件指定大小(需要用到File對象的length()方法:其實也可以通過name再構造一個File對象),推薦使用FileInputStream(String name),它最為簡潔。
- FileInputStream對象一旦進行實例化,就會立刻進行文件的打開,所以如果文件不存在,會拋出FileNotFoundException異常(屬於IOException)。所以該流使用結束后必須調用close()方法來進行關閉,進行資源釋放。
- read()方法一次從文件輸入流中讀取一個字節,並返回int類型,並不返回byte類型,因為具體讀取到的字節屬於屬於-128到127(占8位),直接返回的話,難以判定是讀取到的字節數是-1,還是讀取到結尾了,可能造成讀取中斷問題。所以返回int的話,會將8位的-1轉化為32位int類型的255,避免沖突。read(byte[] b)以及read(byte[] b, int off, int len)方法一次讀取數個字節,返回讀取到的字節數,讀取到結尾時返回-1。
- read()方法是阻塞的。即線程運行到read()處,該線程必須等待read()完成才能進行該線程后續代碼執行操作。在網絡通信狀態不穩定或者socket等待時,往往配合多線程進行處理。read(byte[] b)以及read(byte[] b, int off, int len)同理。
- 對於文件流的讀取,推薦使用read(byte[] b, int off, int len)來處理,可以減少讀取次數,取得高效。
- for(int i=0;(i=is.read())!=-1;){} 塊可用於循環讀取輸入流的字節,當讀取到的結尾時返回-1。也可以用int i=0;while((i=is.read())!=-1){}塊,效果同樣(只是多了一行)。
OutputStream字節輸入流,其主要方法有:
void close():釋放流對象。用於關閉資源。close()后無法進行write及flush等操作。所以一般在流操作結束后進行close()調用。
void flush():刷新輸出流,以便數據能夠完全被寫入。
void write(byte[] b):一次寫入所給的字節數組的所有數據。
void write(byte[] b, int off, int len):一次寫入所給的字節數組從off位置開始,長度為len的數據。
abstract void write(int b):一次寫入一個字節。
輸出字節流InputStream用於“寫入”,其中最常用的方法是:write(byte[] b) , write(byte[] b, int off, int len) , write(int b)
測試代碼:
1 import java.io.FileOutputStream; 2 import java.io.IOException; 3 import java.io.OutputStream; 4 5 public class File002 { 6 public static void main(String[] args) throws IOException { 7 String filepath = "G:/java2019/file123.txt"; 8 //打開文件構造輸出流,如果文件不存在則進行創建,如果文件上級路徑不存在,則拋出FileNotFoundException 9 OutputStream os = new FileOutputStream(filepath); 10 11 os.write(97);//寫入a 12 os.write(98);//寫入b 13 os.write('c');//寫入c 14 15 byte[] b = new byte[]{97,98,99}; 16 17 os.write(b); 18 19 os.write(b, 2, 1); 20 os.write(b, 1, 1); 21 os.write(b, 0, 1); 22 23 //關閉輸出流 24 os.close(); 25 26 //再次打開,需要再次構造。在文件內容后邊進行寫入。 27 os = new FileOutputStream(filepath, true); 28 String LINE = System.getProperty("line.separator"); 29 byte[] bLine = null; 30 if(LINE.equals("\r")){ 31 bLine = new byte[]{10}; 32 }else if(LINE.equals("\n")){ 33 bLine = new byte[]{13}; 34 }else{// LINE.equals("\r\n") 35 bLine = new byte[]{10,13}; 36 } 37 38 //輸出換行 39 os.write(bLine); 40 41 os.write(new byte[]{'A','B','C'}); 42 43 //關閉輸出流 44 os.close(); 45 46 } 47 }
文件內容:
abcabccba
(這時是空白行)
ABC
說明:
- FileOutputStream是文件字節輸出流。其構造方法有:FileOutputStream(File file) ,FileOutputStream(File file, boolean append) ,FileOutputStream(String name),FileOutputStream(String name, boolean append), FileOutputStream(FileDescriptor fdObj)。其中FileOutputStream(String name)及FileOutputStream(String name, boolean append)最為常用,它其實調用的也是FileOutputStream(File file)或FileOutputStream(File file, boolean append)。如果沒有什么獲取文件指定大小(需要用到File對象的length()方法:其實也可以通過name再構造一個File對象),推薦使用FileoutputStream(String name)及FileOutputStream(String name, boolean append),它最為簡潔。boolean類型的參數append指明對文件內容的寫入是否要接在文件末尾,如果不想覆蓋原來文件的內容,則需要使用FileOutputStream(name,true),因為默認情況下是false(會覆蓋)。
- FileOutputStream對象一旦進行實例化,就會立刻進行文件的打開,所以如果文件不存在,會自動創建,如果文件所在路徑(上級目錄)不存在,會拋出FileNotFoundException異常(屬於IOException)。所以該流使用結束后必須調用close()方法來進行關閉,進行資源釋放。
- write(int b)方法一次向文件輸出流中寫入一個字節,int類型會最終轉化為byte類型。write(byte[] b)以及write(byte[] b, int off, int len)方法一次寫入數個字節。
- write()方法是不會阻塞的。wrie(byte[] b)以及write(byte[] b, int off, int len)同理。
- 對於文件流的寫入,推薦使用write(byte[] b, int off, int len)來處理,可以減少寫入次數,取得高效。
- byte[] b=new byte[1024*8];int len=0;while((len=is.read(b))!=-1){os.write(b,0,len);}塊可以配合讀取、寫入操作,進行高效的文件的復制。
高效的字節文件復制操作代碼:
1 import java.io.FileInputStream; 2 import java.io.FileOutputStream; 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.OutputStream; 6 7 public class File003 { 8 public static void main(String[] args) throws IOException { 9 String from = "G:/java2019/file.txt"; 10 String to = "G:/java2019/file2.txt"; 11 InputStream is = new FileInputStream(from); 12 OutputStream os = new FileOutputStream(to); 13 14 byte[] b = new byte[1024*8]; 15 int len = 0; 16 while((len=is.read(b))!=-1){ 17 os.write(b, 0, len); 18 } 19 20 is.close(); 21 os.close(); 22 } 23 }
IO中,要處理具體的異常,可以這樣:
1 import java.io.FileInputStream; 2 import java.io.FileOutputStream; 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.OutputStream; 6 7 public class File004 { 8 public static void main(String[] args) { 9 String from = "G:/java2019/file.txt"; 10 String to = "G:/java2019/file2.txt"; 11 InputStream is = null; 12 OutputStream os = null; 13 try { 14 is = new FileInputStream(from); 15 os = new FileOutputStream(to); 16 17 byte[] b = new byte[1024 * 8]; 18 int len = 0; 19 while ((len = is.read(b)) != -1) { 20 os.write(b, 0, len); 21 } 22 23 } catch (IOException e) { 24 e.printStackTrace(); 25 } finally { 26 if (is != null) { 27 try { 28 is.close(); 29 } catch (IOException e) { 30 e.printStackTrace(); 31 } 32 } 33 34 if (os != null) { 35 try { 36 os.close(); 37 } catch (IOException e) { 38 e.printStackTrace(); 39 } 40 } 41 } 42 43 } 44 }
不過這樣很煩索,如果在JDK1.7及以上版本,可以使用try-resource改進,代碼:
1 import java.io.FileInputStream; 2 import java.io.FileOutputStream; 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.OutputStream; 6 7 public class File005 { 8 public static void main(String[] args) { 9 String from = "G:/java2019/file.txt"; 10 String to = "G:/java2019/file2.txt"; 11 try (InputStream is = new FileInputStream(from);OutputStream os = new FileOutputStream(to);) { 12 byte[] b = new byte[1024 * 8]; 13 int len = 0; 14 while ((len = is.read(b)) != -1) { 15 os.write(b, 0, len); 16 } 17 } catch (IOException e) { 18 e.printStackTrace(); 19 } 20 21 } 22 }
JAVA也提供了比較高效的內置緩存流:BufferedInputStream及BufferedOutputStream。其內置緩沖區大小默認為1024*8(如果需要改變,可以在構造的時候傳入其它值)。它們直接由InputSteam及OutputStream構造,所有方法都與InputStream及OutputStream相同。不過不同的是對於OutputStream,其write()實際上是寫入到緩沖區,然后判斷緩沖區是否滿了,如果滿了則使用flush()刷新緩沖區,同時寫入文件,如果最后一次的時候剛才未滿,則不能保證是否寫入,這樣會造成斷尾了。可以使用其flush()方法,或者當進行close()的時候,也會進行緩沖區的刷新及文件的寫入(不過對於JDK1.7及以上版本使用try-resource處理異常的情況下,會自動進行close(),不太需要擔心)
BufferedInputStream構造器:
public BufferedInputStream(InputStream in):以字節輸入流構造緩沖輸入流,默認1024*8的緩沖區,常用。
public BufferedInputStream(InputStream in, int size) :以字節輸入流構造緩沖輸入流,緩沖區大小由size指定。
BufferedOutputStream構造器:
public BufferedOutputStream(OutputStream out):以字節輸出流構造緩沖輸出流,默認1024*8的緩沖區,常用。
public BufferedOutputStream(OutputStream out, int size):以字節輸出流構造緩沖輸出流,緩沖區大小由size指定。
測試代碼:
1 import java.io.BufferedInputStream; 2 import java.io.BufferedOutputStream; 3 import java.io.FileInputStream; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 7 public class File006 { 8 public static void main(String[] args) { 9 String from = "G:/java2019/file.txt"; 10 String to = "G:/java2019/file2.txt"; 11 try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(from)); 12 BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(to));) { 13 byte[] b = new byte[1024 * 8]; 14 int len = 0; 15 while ((len = is.read(b)) != -1) { 16 os.write(b, 0, len); 17 } 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 22 } 23 }
說明:
可以知道,使用字節緩沖流處理輸入輸出的時候,跟之前的代碼相比,只需要改變構造器就可以,其它代碼可以一概不變,因為緩沖字節流使用的是字節流對象構造並且直接重寫其所有方法。
要進行高效的字節流處理,一方面可以使用緩沖字節流,另一方面可以使用自己寫的高效處理方法(同上邊“字節文件的復制”)。推薦使用后者,更靈活,更高效。
除了InputStream與OutputStream的應用中,除了FileInputStream與FileOutputStream常用來處理文件,也可以處理字節數組,數據,序列對象,管道,字符緩沖等。
ByteArrayInputStream可以從字節數組中獲取字節輸入流數據,ByteArrayOutputStream可以將數據寫入字節數組輸出流。代碼:
1 import java.io.ByteArrayInputStream; 2 import java.io.ByteArrayOutputStream; 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.OutputStream; 6 7 public class File007 { 8 public static void main(String[] args) { 9 byte[] b = new byte[] {97,98,99}; 10 try(InputStream is = new ByteArrayInputStream(b); OutputStream os = new ByteArrayOutputStream()){ 11 int i = 0; 12 while((i=is.read())!=-1){ 13 os.write(i); 14 } 15 16 System.out.println(os.toString()); 17 } catch (IOException e) { 18 e.printStackTrace(); 19 } 20 21 } 22 }
DataInputStream可以從int,long,float,double,char,byte[],boolean等中獲取字節輸入流的數據,DataOutputStream可以將int,long,float,double,char,byte[],boolean等數據寫入字節輸出流。代碼:
1 public class File007 { 2 public static void main(String[] args) { 3 byte[] b = new byte[] { 97, 98, 99, 100 }; 4 try (DataInputStream is = new DataInputStream(new ByteArrayInputStream(b)); 5 DataOutputStream os = new DataOutputStream(new FileOutputStream("g:/java2019/file.txt"))) { 6 char c = 0; 7 c = is.readChar();// 讀取兩個字節 8 os.writeChar(c);// 寫入兩個字節 9 System.out.println(c); 10 c = is.readChar(); 11 os.writeChar(c); 12 System.out.println(c); 13 } catch (IOException e) { 14 e.printStackTrace(); 15 } 16 17 } 18 }
輸出:
慢
捤
文件內容:abcd
ObjectInputStream可以從已經序列化的對象(對象相對應的某種格式化的文件)等中獲取字節輸入流的數據,DataOutputStream可以將序列化的對象(實現Serialiazable接口的對象)寫入字節輸出流(最終變成相應格式文件數據)。代碼:
測試代碼:
1 import java.io.FileInputStream; 2 import java.io.FileOutputStream; 3 import java.io.IOException; 4 import java.io.ObjectInputStream; 5 import java.io.ObjectOutputStream; 6 import java.io.Serializable; 7 8 public class File008 { 9 public static void main(String[] args) { 10 try (ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("g:/java2019/file.txt"))) { 11 User user = new User(); 12 user.setId(1); 13 user.setName("dreamyoung"); 14 os.writeObject(user); 15 } catch (IOException e) { 16 e.printStackTrace(); 17 } 18 19 try(ObjectInputStream is = new ObjectInputStream(new FileInputStream("g:/java2019/file.txt"))){ 20 try { 21 User user = (User)is.readObject(); 22 System.out.println(user); 23 } catch (ClassNotFoundException e) { 24 e.printStackTrace(); 25 } 26 } catch (IOException e) { 27 e.printStackTrace(); 28 } 29 30 } 31 } 32 33 class User implements Serializable{ 34 private static final long serialVersionUID = 1L; 35 private int id; 36 private String name; 37 public int getId() { 38 return id; 39 } 40 public void setId(int id) { 41 this.id = id; 42 } 43 public String getName() { 44 return name; 45 } 46 public void setName(String name) { 47 this.name = name; 48 } 49 50 @Override 51 public String toString() { 52 return "User [id=" + id + ", name=" + name + "]"; 53 } 54 }
輸出:
User [id=1, name=dreamyoung]
文件內容:
sr User I idL namet Ljava/lang/String;xp t
dreamyoung
說明:
- 對象序列化通常指的是將對象數據(不是類)保存到文件,其格式由JVM自動生成。而反序列化指的是從文件中讀取數據生成對象。
- 要序列化的對象必須實現Serializable接口(通常還可以添加serialVersionUID),不然會出現 NotSerializableException 異常。
SequenceInputStream可以從已經一個或多個的輸入流中順序獲取字節輸入流的數據。需要注意的是,沒有SequenceOutputStream。代碼:
1 import java.io.FileInputStream; 2 import java.io.FileOutputStream; 3 import java.io.IOException; 4 import java.io.SequenceInputStream; 5 6 public class File009 { 7 public static void main(String[] args) { 8 //通過順序流,將兩個文件內容寫入一個新文件 9 try (FileInputStream fis1 = new FileInputStream("g:/java2019/file1.txt");//文件內容:111 10 FileInputStream fis2 = new FileInputStream("g:/java2019/file2.txt");//文件內容:222 11 SequenceInputStream is = new SequenceInputStream(fis1, fis2); 12 FileOutputStream fos = new FileOutputStream("g:/java2019/file3.txt")) { 13 byte[] b = new byte[1024]; 14 int len = 0; 15 while((len=is.read(b))!=-1){ 16 fos.write(b,0,len); 17 } 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 22 } 23 }
文件file3.txt內容:111222
要進行文件分割,必須配套PushbackInputStream類來使用,它可以使用 unread(int b) 或 unread(byte[] b) 或 unread(byte[] b, int off, int len)將已經讀取的內容返送回去,對於分割讀取后超過的內容,則可以不讀取。注意默認只能返回讀一個字節,如果要推回的內容長度比較大,還是建議指定長度,否則會出現Push back buffer is full異常。分割文件的代碼:
1 import java.io.File; 2 import java.io.FileInputStream; 3 import java.io.FileOutputStream; 4 import java.io.IOException; 5 import java.io.PushbackInputStream; 6 import java.util.Arrays; 7 import java.util.Iterator; 8 import java.util.List; 9 10 public class File011 { 11 public static void main(String[] args) { 12 // 沒有SequenceOutputStream,要分割文件,可以采用如下手段: 13 File file1 = new File("g:/java2019/file1.txt"); 14 File file2 = new File("g:/java2019/file2.txt"); 15 File file3 = new File("g:/java2019/file3.txt"); 16 17 try (FileOutputStream fos1 = new FileOutputStream(file1); 18 FileOutputStream fos2 = new FileOutputStream(file2); 19 FileOutputStream fos3 = new FileOutputStream(file3); 20 PushbackInputStream fis = new PushbackInputStream(new FileInputStream("g:/java2019/file.txt"), 1024)) { 21 // file.txt內容:111111222222333333 22 byte[] b = new byte[5];// 緩沖數組大小,不大於分割文件大小 23 int splitSize = 6;// 分割文件大小 24 int len = 0; 25 List<FileOutputStream> list = Arrays.asList(fos1, fos2, fos3); 26 Iterator<FileOutputStream> it = list.iterator(); 27 while (it.hasNext()) { 28 FileOutputStream fos = (FileOutputStream) it.next(); 29 while (fos.getChannel().size() < splitSize && (len = fis.read(b)) != -1) { 30 int left = (int) fos.getChannel().size() + len - splitSize; 31 32 if (left > 0) {// 讀取后會超出 33 for(int i=0;i<left;i++){ 34 fis.unread(b[len-left+i]); 35 } 36 //fis.skip(-left); 37 fos.write(b, 0, len - left); 38 } else { 39 fos.write(b, 0, len); 40 } 41 42 System.out.println(fos.toString() + ":" + fos.getChannel().size()); 43 } 44 } 45 46 } catch (IOException e) { 47 e.printStackTrace(); 48 } 49 50 }
輸出:
java.io.FileOutputStream@15db9742:5
java.io.FileOutputStream@15db9742:6
java.io.FileOutputStream@6d06d69c:5
java.io.FileOutputStream@6d06d69c:6
java.io.FileOutputStream@7852e922:5
java.io.FileOutputStream@7852e922:6
分割后各文件內容為:
file1.txt: 111111
file2.txt: 222222
file3.txt: 333333
PipedInputStream可以從管道中獲取字節輸入流數據,PipedOutputStream可以將數據寫入管道輸出流。將在網絡編程的時候進行說明,本篇暫不介紹。
總結:
- FileInputStream與FileOutputStream可以以字節流形式處理文件,進行讀取或寫入操作。
- 要進行高效的字節流處理,可以使用內置的BufferedInputStream及BufferedOutputStream緩沖字節流,它可以處理各種InputStream及OutputStream(不僅僅是FileInputStream及FileOutputStream)
- 使用read(byte[] b, int off, int len)及write(byte[], int off, int len) 可以更高效的進行字節復制(可以替代緩沖字節流)
流