在Java中,可從中讀出一系列數據的對象稱爲“輸入流(InputStream)”,而能向其中寫入一系列數據的對象稱爲“輸出流(OutputStream)”。Java的輸出/輸入都是通過繼承抽象類InputStream和OutputStream(面向字節)、Reader和writer(面向字符)來實現的。
一、IO流對象層次關係
二、IO 基本操作
在java.io包中流的操作主要有字節流、字符流兩大類,兩者都有輸入和輸出的操作。在字節流中輸出數據主要使用OutputStream類完成,輸入使用InputStream類。在字符流中輸出主要是使用Writer類完成,輸入主要使用Reader類完成。
在Java中IO操作也有相應的步驟,以文件的操作爲例,主要的操作流程如下:
(1) 使用File類打開一個文件。
(2) 通過字節流或字符流的子類指定輸出位置。
(3) 進行讀/寫操作。
(4) 關閉輸入/輸出。
下面分別介紹如何使用字節流或字符流保存或讀取數據。
三、字節流
字節流主要存操作byte類型數據,以byte數組爲準,主要操作類是OutputStream類和InputStream類。
1.字節輸出流:OutputStream
OutputStream是整個IO包中字節輸出流的最大父類,此類的定義如下:
public abstract class OutputStream extends Objectimplements Closeable, Flushable
從以上定義中可以發現,OutputStream是一個抽象類,如果要是使用此類,則首先必須通過子類實例化對象。如果現在操作的是一個文件,則可以使用FileOutputStream類,通過向上轉型後,可以爲OutputStream實例化,在OutputStream類中的主要操作方法:
// 關閉此輸出流並釋放與此流有關的所有系統資源
public void close() throws IOException
// 刷新此輸出流並強制寫出所有緩衝的輸出字節。
public void flush() throws IOException
// 將 b.length 個字節從指定的 byte 數組寫入此輸出流
public void write(byte[] b) throws IOException
// 將指定 byte 數組中從偏移量 off 開始的 len 個字節寫入此輸出流
public void write(byte[] b, int off, int len) throws IOException
範例:向文件中寫入字符串。
public class OutputStreamDemo {
/**
* 向文件中寫入字符串
*/
public static void main(String[] args) throws IOException {
// 1.使用File類,找到一個將字符串寫入的文件
File file = new File("E:" + File.separator + "ops.txt");
// 2.通過子類FileOutputStream實例化OutputStream
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
// 3.進行寫操作
String str = "Hello world!";
byte[] b = str.getBytes();
outputStream.write(b);
} finally {
try {
// 4.關閉輸出流
outputStream.close();
} catch (IOException e) {
throw new RuntimeException("關閉字符輸出流失敗!");
}
}
}
}
①提問:通過上面的例子,我們發現將字符串寫入到文件中,但是我們再次運行時會發現,新寫入的字符串將過去的覆蓋了,我們該如何解決了?
回答:
此時我們應該使用FileOutPutStream的另外一個構造方法:
// 如果第二個參數爲 true,則將字節寫入文件末尾處,而不是寫入文件開始處。
public FileOutputStream(File file, boolean append) throws FileNotFoundException
②提問:如何增加換行?
回答:如果要換行,則直接在字符串要換行處加一個"\r\n"。在windows操作系統中“\r\n”表示換行,但是在Linux操作系統中“\n”表示換行,解決辦法,如下:
private static final String FILE_SEPARATOR = System.getProperty("line.separator");
2.字節輸入流
既然程序可以向文件中寫入內容,則也可以通過InputStream從文件中將內容讀取出來。InputStream類的定義如下:
public abstract class InputStreamextends Objectimplements Closeable
與OutputStream類一樣,InputStream本身也是一個抽象類,需要依靠子類來實現,如果從文件中讀取數據,子類是FileInputStream。InputStream的主要方法:
// 返回此輸入流下一個方法調用可以不受阻塞地從此輸入流讀取(或跳過)的估計字節數
public int available() throws IOException
// 關閉此輸入流並釋放與該流關聯的所有系統資源。
public void close() throws IOException
// 將輸入流中最多 len 個數據字節讀入 byte 數組
public int read(byte[] b, int off, int len) throws IOException
範例:從文件中讀取內容。
public class InputStreamDemo {
public static void main(String[] args) throws IOException {
// 1.使用File類,找到一個將字符串寫入的文件
File file = new File("E:" + File.separator + "ops.txt");
InputStream inputStream = null;
try {
// 2.實例化字節輸入流 InputStream對象
inputStream = new FileInputStream(file);
// 3.進行讀操作
byte[] bs = new byte[1024];
int len = 0;
while ((len = inputStream.read(bs)) != -1) {
System.out.println(new String(bs, 0, len));
}
} finally {
try {
// 4.關閉資源
inputStream.close();
} catch (IOException e) {
throw new RuntimeException("關閉輸入字節流失敗!");
}
}
}
}
①問題:上面程序中,開闢的byte數據大小爲1024,而實際的內容只有12個字節,顯然開闢的空間太大,該怎麼解決了?
回答:
byte[] bs = new byte[(int) file.length()];
此程序明確知道了要讀取的內容爲14字節,所以可以定義(int)file.length()大小的空間,如果不確定大小,不建議使用此種方法。
四、字符流
在程序中一個字符等於兩個字節,那麼Java提供了Reader和Writer兩個專門操作字符流的類。
1.字符輸出流Writer
此類的定義如下:
public abstract class Writer extends Objectimplements Appendable, Closeable, Flushable
關於對Appendable接口的說明,此接口的定義如下:
public interface Appendable {
Appendable append(char c) throws IOException;
Appendable append(CharSequence csq, int start, int end) throws IOException
Appendable append(CharSequence csq) throws IOException
}
此接口表示的是內容可以被追加,接受的參數是CharSequence,實際上String類也實現了此接口,所以可以直接通過此接口的方法向輸出流中追加內容。
Writer的主要方法:
// 關閉此流,但要先刷新它
public abstract void close()throws IOException
// 刷新該流的緩衝
public abstract void flush() throws IOException
// 寫入字符串
public void write(String str) throws IOException
// 寫入字符數組
public void write(char[] cbuf) throws IOException
範例:向文件中寫入數據
File file = new File("E:" + File.separator + "w.txt");
Writer out = new FileWriter(file);
String str = "hello world!";
out.write(str);
out.close();
2.字符輸入流Reader
Reader是使用字符的方式從文件中讀取數據,Reader類的定義如下:
public abstract class Reader extends Objectimplements Readable, Closeable
Reader類的常用方法:
// 關閉該流並釋放與之關聯的所有資源
public abstract void close()throws IOException
// 讀取單個字符
public int read() throws IOException
// 將字符讀入數組
public int read(char[] cbuf) throws IOException
範例:從文件中讀取內容
File file = new File("E:" + File.separator + "w.txt");
Reader reader = new FileReader(file);
char[] c = new char[1024];
int len = 0;
while ((len = reader.read(c)) != -1) {
System.out.println(new String(c, 0, len));
}
reader.close();
五、字節流與字符流的區別
字節流與字符流使用非常相似,兩者除了操作代碼上的不同之外,是否還有其他的不同了?
實際上字節流在操作時本身不會用到緩衝區(內存),是文件本身操作的,二字符流在操作時使用了緩衝區,通過緩衝區在操作文件。
下面一兩個寫文件的操作爲主進行比較,但是在操作時字節流和字符流的操作完成之後都不關閉輸出流。
範例:使用字節流不關閉執行
File file = new File("E:" + File.separator + "ops.txt");
OutputStream outputStream = new FileOutputStream(file);
String str = "Hello world!";
byte[] b = str.getBytes();
outputStream.write(b);
// 關閉輸出流
// outputStream.close();
沒有關閉字節流操作,但是文件中也依然存在輸出的內容,證明字節流是直接操作文件本身的。下面使用字符流,觀察效果
範例:使用字符流不關閉執行
File file = new File("E:" + File.separator + "w.txt");
Writer out = new FileWriter(file);
String str = "hello world!";
out.write(str);
// 關閉輸出流
// out.close();
程序運行之後發現文件中沒有任何內容,這是因爲字符流操作使用了緩衝區,而在關閉字符流時會強制性的將緩衝區中的內容進行輸出,但是如果程序沒有關閉,則緩衝區中的內容是無法輸出的,所以得出結論:字符流使用緩衝區,而字節流沒有使用緩衝區。
①問題:什麼是緩衝區?
回答:可以簡單地把緩衝區理解爲一段特殊的內存。
某些情況下,一個程序頻繁地操作一個資源(如文件),則性能會很低,此時爲了提升性能,就可以將一部分數據暫時讀入到內存的一塊區域中,以後直接從此區域中讀取數據即可,因爲讀內存速度比較快,這樣提高性能。
強制性清空緩衝區
OutputStream的flush()方法。
②問題:使用字節流好還是字符流好?
回答:字節流更好。所有的文件在硬盤或者在傳輸時都是以字節的方式進行的,包括圖片等,而字符是只有在內存中才使用。
範例:文件複製
1.實現要求,在dos命令中存在一個文件的複製命令(copy),copy命令的語法格式如下:
copy 源文件 目標文件
下面使用Java完成以上功能,程序運行時可以按照如下格式進行:
java Copy 源文件 目標文件
2.思路,從運行格式中發現,要輸入源文件和目標文件的路徑,可以使用命令行參數完成,必須對輸入的參數進行驗證,如果輸入的參數不是兩個,或者源文件不存在,則程序給出錯誤信息並退出。
是選擇字節流還是字符流,因爲要複製的文件不一定都是文本文件,也可能包含圖片或者聲音,所以使用字節流。
兩種操作方式:
將源文件中的內容全部讀取到內存,並一次性寫入到目標文件中。
不要將源文件中的內容全部都取進來,而是使用邊讀邊寫的方式。
很顯然,使用邊讀邊寫更好,文件內容過多,則內存是無法裝的。
public class Copy {
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("輸入的參數不正確!");
System.out.println("例:java Copy 源文件路徑 目標文件路徑");
System.exit(1); // 系統退出
}
File source = new File(args[0]); // 源文件的File對象
File target = new File(args[1]); // 目標文件的File對象
if (!source.exists()) {
System.out.println("源文件不存在!");
System.exit(1);
}
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream(source);
outputStream = new FileOutputStream(target);
if (inputStream != null && outputStream != null) {
int len = 0;
byte[] bs = new byte[1024];
while ((len = inputStream.read(bs)) != -1) {
outputStream.write(bs, 0, len);
}
System.out.println("複製完成!");
}
} catch (IOException e) {
System.out.println("複製失敗!");
throw new RuntimeException(e.getMessage());
}
finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
throw new RuntimeException("輸出關閉流失敗!");
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException("輸入關閉流失敗!");
}
}
}
}
}