JavaIO流
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
- 基於記憶體:ByteArrayInputStreamByteArrayOutputStream(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(); }
列印流實現檔案拷貝操作和位元組流差不多,除了用到列印流建構函式的不自動重新整理。列印流還有一個好處就是無需檢查異常。