Java中執行輸出和輸入操作,需要通過IO流。例如最常見的System.out.println()就是一個輸出流。IO流的類比較多,但核心體系就是由File、 InputStream 、OutputStream、Reader、Writer和Serializable(接口)組成的,后續會一一詳細說明。
I/O流基礎概念
按照流的方向分為輸入流(InputStream)與輸出流(OuputStream):
- 輸入流:只能讀取數據,不能寫入數據。
- 輸出流:只能寫入數據,不能讀取數據。
因為程序是運行在內存中,以內存角度來理解輸入輸出概念,如下:

可以看到輸入與輸出是一個相對概念,數據寫入文件,對於程序來說是輸出流,對文件來說是輸入流。但一般是以程序作為中心,所以從程序寫入數據到其他位置,則是輸出流,將數據讀入程序中則是輸入流。
簡單的說就是:讀取數據就是輸入流,寫入數據就是輸出流。
按照處理的數據單位分為字節流和字符流
- 字節流:操作的數據單元是8位的字節。InputStream、OutputStream作為抽象基類。
- 字符流:操作的數據單元是字符。以Writer、Reader作為抽象基類。
- 字節流可以處理所有數據文件,若處理的是純文本數據,建議使用字符流。
IO流中的三類數據源
- 基於磁盤文件:FileInputStream、FileOutputSteam、FileReader、FileWriter
- 基於內存:ByteArrayInputStream ByteArrayOutputStream(ps:字節數組都是在內存中產生)
- 基於網絡:SocketInputStream、SocketOutputStream(ps:網絡通信時傳輸數據)
根據流的作用可分為節點流和處理流
節點流:程序直接與數據源連接,和實際的輸入/輸出節點連接;處理流:對節點流進行包裝,擴展原來的功能,由處理流執行IO操作。
處理流的作用和分類:
處理流可以隱藏底層設備上節點流的差異,無需關心數據源的來源,程序只需要通過處理流執行IO操作。處理流以節點流作為構造參數。通常情況下,推薦使用處理流來完成IO操作。
緩沖流:提供一個緩沖區,能夠提高輸入/輸出的執行效率,減少同節點的頻繁操作。例如:BufferedInputStream/BufferedOutputStream、BufferedReader/BufferWriter
轉換流:將字節流轉成字符流。字節流使用范圍廣,但字符流更方便。例如一個字節流的數據源是純文本,轉成字符流來處理會更好。InputStreamReader/OutputStreamWriter
打印輸出流:打印輸出指定內容,根據構造參數中的節點流來決定輸出到何處。
PrintStream :打印輸出字節數據。 PrintWriter : 打印輸出文本數據。
附圖:JavaIO體系的全體類

介紹完基礎概念后,使用IO流來完成一些簡單功能:
(一)使用字節流讀取本地文件
//File對象定位數據源
public static void getContent(File file) throws IOException {
//創建文件緩沖輸入流
file BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
byte[] buf = new byte[1024];//創建字節數組,存儲臨時讀取的數據
int len = 0;//記錄數據讀取的長度
//循環讀取數據
while((len = bis.read(buf)) != -1) { //長度為-1則讀取完畢
System.out.println(new String(buf,0,len));
}
bis.close(); //關閉流
}
【技巧】如果數據源是純文本數據,使用字符流效率更高。
(二)使用字符處理流讀取本地文件內容
public static void getContent(String path) throws IOException {
File f = new File(path);
if (f.exists()) { // 判斷文件或目錄是否存在
if (f.isFile()) {
BufferedReader br = new BufferedReader(new FileReader(path));//該緩沖流有一個readLine()獨有方法
String s = null;
while ((s = br.readLine()) != null) {//readLine()每次讀取一行
System.out.println(s);
}
}
}
}
該方法比上一個增加了文件判斷,提高了程序的健壯性。使用了BufferedReader處理流來處理純文本數據,比字節流更加簡潔方便。
(三)使用字符流寫入數據到指定文件:
public static void main(String[] args) throws IOException {
//以標准輸入作為掃描來源
Scanner sc = new Scanner(System.in);
File f = new File("D:\\reviewIO\\WRITERTest.txt");
BufferedWriter bw = new BufferedWriter(new FileWriter(f));
if(!f.exists()) {
f.createNewFile();
}
while(true) {
String s = sc.nextLine();
bw.write(s);
bw.flush();
if(s.equals("結束") || s.equals("")) {
System.out.println("寫入數據結束!");
return;
}
}
}
(四)使用轉換流(InputStreamReader/OutputStreamWriter),對寫入數據進行改進:
public static void testConvert(File f) throws IOException {
if(!f.exists()) {
f.createNewFile();
}
//以System.in作為讀取的數據源,即從鍵盤讀取
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new FileWriter(f,true)); //允許添加內容,不會清除原有數據源
String s = null;
while(!(s = br.readLine()).equals("")) {
bw.write(s);
bw.newLine();//空一行
}
bw.flush();
bw.close();
br.close();
}
因為System.in是一個InputStream對象,緩沖字符流無法直接使用,需要通過轉換流將字節流轉成字符流。然后使用字符輸入處理流的readLine()每次讀取一行,使用newLine()完成換行。
注意點:通常使用IO流寫入文件時,寫入的數據總會覆蓋原來的數據,這是因為文件輸出流默認不允許追加內容,所以需要為FileOuputStream、FileWriter的構造參數boolean append 傳入true。
//字節流實現文件拷貝
public static String copyFile(String src, String dest) throws IOException, ClassNotFoundException {
File srcFile = new File(src);//源文件數據源
File desFile = new File(dest);//寫入到目標數據源
//數據源不存在
if(!srcFile.exists() || !desFile.exists()) {
throw new ClassNotFoundException("源文件或者拷貝目標文件地址不存在!");
}
//非文件類型
if(!srcFile.isFile() || !desFile.isFile()) {
return "源文件或者目標文件不是文件類型!";
}
InputStream is = null;
OutputStream os = null;
byte[] buf = new byte[1024];//緩存區
int len = 0;//讀取長度
try {
is = new BufferedInputStream(new FileInputStream(srcFile));//讀取數據源
os = new BufferedOutputStream(new FileOutputStream(desFile));//寫入到數據源
while((len = is.read(buf)) != -1) { //讀取長度不為-1,繼續讀取
os.write(buf); //讀取內容之后馬上寫入目標數據源
}
os.flush();//輸出
return "文件拷貝成功!查看拷貝文件路徑:" + desFile.getPath();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(is != null)
is.close();
if(os != null)
os.close();
}
return "文件拷貝失敗";
}
(六)使用打印流來完成寫入數據操作:
//輸出內容的文件數據源
File f = new File("D:\\reviewIO\\PW.java");
PrintWriter pw = new PrintWriter(f);
//把指定內容打印至數據源中
pw.println("AAAAAAAAA");
pw.println("BBBBBBBBB");
pw.println("CCCCCCCCC");
pw.flush();
System.out.println("使用PrintWriter寫入數據完成");
System.out.println("==========讀取寫入的數據==========");
BufferedReader br = new BufferedReader(new FileReader(f));
String s = null;
StringBuilder sb = new StringBuilder();//一個可變字符串
while((s = br.readLine()) != null) {
sb.append(s); //把讀取的字符串組合起來
}
System.out.println(sb);
br.close();
pw.close();
一般情況下,若是輸出文本數據,建議使用打印流。PrintWriter還可以指定輸出文本使用何種字符集、在構造參數中指定是否自動刷新。如果不想覆蓋原來的數據,使用該類的append()方法,就會在文件尾部添加內容。
(七)使用打印流來完成文本拷貝:
// 使用打印流PrintStream來完成文件拷貝
public static void copyFile(File src, File dest) throws Exception {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest));
PrintStream ps = new PrintStream(bos, true);
byte[] buf = new byte[1024];
int len = 0;
//循環讀取數據,然后寫入到目標文件
while ((len = bis.read(buf)) != -1) {
ps.write(buf);
}
ps.close();
bos.close();
}
打印流實現文件拷貝操作和字節流差不多,除了用到打印流構造函數的不自動刷新。打印流還有一個好處就是無需檢查異常。
轉自:https://www.cnblogs.com/fwnboke/p/8529492.html

