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(); }
打印流實現文件拷貝操作和字節流差不多,除了用到打印流構造函數的不自動刷新。打印流還有一個好處就是無需檢查異常。