1. 程式人生 > >理解Java之IO流

理解Java之IO流

流是一種抽象概念,它代表了資料的無結構化傳遞。用來進行輸入輸出操作的流就稱為IO流。

IO流結構

流的分類方式

按流向分:從檔案/網路/記憶體等(資料來源)到程式是輸入流
從程式到檔案/網路/記憶體等(資料來源)是輸出流
按資料處理單位分
位元組流:以位元組為單位傳輸資料的流,以Stream結尾的都是位元組流。
字元流:以字元為單位傳輸資料的流,以Reader結尾的都是輸入字元流,以Writer結尾的都是輸出字元流。
按功能(層次)分
節點流:用於直接操作目標裝置的流
處理流(也叫過濾流):是對一個已存在的流的連線和封裝,通過對資料的處理為程式提供更為強大、靈活的讀寫功能。

IO流的結構

如下圖所示:

IO流的結構
注意:
所有的位元組輸入流類都是InputStream的子類;所有的位元組符輸入流類都是Reader的子類;所有的位元組節輸出流類都是OutputStream的子類;所有的位元組符輸出流類都是Writer的子類,且他們都為抽象類。

IO流四大抽象類

InputStream的基本方法:

 public abstract int read() throws IOException {}//從輸入流中讀取資料的下一個位元組, 返回讀到的位元組值.若遇到流的末尾,返回-1
 public int read(byte[] b) throws IOException {}//從輸入流中讀取 b.length 個位元組的資料並存儲到緩衝區陣列b中.返回的是實際讀到的位元組總數
 public int read(byte[] b, int off, int len) throws IOException {}//讀取 len 個位元組的資料,並從陣列b的off位置開始寫入到這個陣列中
 public void close() throws IOException {}//關閉此輸入流並釋放與此流關聯的所有系統資源
 public int available() throws IOException {}//返回此輸入流下一個方法呼叫可以不受阻塞地從此輸入流讀取(或跳過)的估計位元組數
 public long skip(long n) throws IOException {}//跳過和丟棄此輸入流中資料的 n 個位元組,返回實現路過的位元組數。

OutputStream的基本方法:

public abstract void write(int b) throws IOException {}//將指定的位元組寫入此輸出流。
public void write(byte[] b) throws IOException {}// 將 b.length 個位元組從指定的 byte 陣列寫入此輸出流。
public void write(byte[] b, int off, int len) throws IOException {}//將指定 byte 陣列中從偏移量 off 開始的 len 個位元組寫入此輸出流。
public void flush() throws IOException {}//重新整理此輸出流並強制寫出所有緩衝的輸出位元組。
pulbic void close() throws IOException {}//關閉此輸出流並釋放與此流有關的所有系統資源。

Reader的基本方法:

public int read() throws IOException {}//讀取單個字元,返回作為整數讀取的字元,如果已到達流的末尾返回-1
public int read(char[] cbuf) throws IOException {}//將字元讀入陣列,返回讀取的字元數
public abstract int read(char[] cbuf, int off, int len) throws IOException {}//讀取 len 個字元的資料,並從陣列cbuf的off位置開始寫入到這個陣列中
public abstract void close() throws IOException {}//關閉該流並釋放與之關聯的所有資源
public long skip(long n) throws IOException {}//跳過n個字元。
public int available()  //還可以有多少能讀到的位元組數

Writer的基本方法:

public void write(int c) throws IOException {} //寫入單個字元
public void write(char[] cbuf) throws IOException {} //寫入字元陣列
public abstract void write(char[] cbuf, int off, int len) throws IOException {} //寫入字元陣列的某一部分
public void write(String str) throws IOException {} //寫入字串
public void write(String str, int off, int len) throws IOException {}//寫字串的某一部分
public abstract void close() throws IOException {}  //關閉此流,但要先重新整理它
public abstract void flush() throws IOException {}  //重新整理該流的緩衝,將緩衝的資料全寫到目的地

IO流的具體使用

FileInputStream 和 FileOutputStream

public static void fileInputStreamTest() {
    File f1 = new File("D:\\in.txt");
    File f2 = new File("D:\\out.txt");
    try {
        FileInputStream fi = new FileInputStream(f1); 
        FileOutputStream fo = new FileOutputStream(f2);
        byte[] buf = new byte[521];
        int len = 0;
        while((len = fi.read(buf)) != -1){
            fo.write(buf, 0, len);
        }
        fo.flush();
        fo.close();
        fi.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

PipedOutputStream和PipedintputStream

Java裡的管道輸入流PipedInputStream與管道輸出流PipedOutputStream實現了類似管道的功能,用於不同執行緒之間的相互通訊。
Java的管道輸入與輸出實際上使用的是一個迴圈緩衝陣列來實現,這個陣列預設大小為1024位元組。輸入流PipedInputStream從這個迴圈緩衝陣列中讀資料,輸出流PipedOutputStream往這個迴圈緩衝陣列中寫入資料。當這個緩衝陣列已滿的時候,輸出流PipedOutputStream所在的執行緒將阻塞;當這個緩衝陣列首次為空的時候,輸入流PipedInputStream所在的執行緒將阻塞。Java在它的jdk文件中提到不要在一個執行緒中同時使用PipeInpuStream和PipeOutputStream,這會造成死鎖。

public class WriteThread implements Runnable{
    private PipedOutputStream pout;  

    WriteThread(PipedOutputStream pout){  
      this.pout=  pout;  
    }  

    @Override
    public void run() {
        try {  
            System.out.println("W:開始將資料寫入:但等個5秒讓我們觀察...");  
            Thread.sleep(5000);  //釋放cpu執行權5秒  
            pout.write("writePiped 資料...".getBytes());  //管道輸出流  
            pout.close();  
          } catch(Exception e) {  
            throw new RuntimeException("W:WriteThread寫入失敗...");  
          }  
    }
}

public class ReadThread implements Runnable{
    private PipedInputStream pin;  

    ReadThread(PipedInputStream pin) {  
      this.pin=pin;  
    }  

    @Override
    public void run() {  //由於必須要覆蓋run方法,所以這裡不能拋,只能try  
      try {  
            System.out.println("R:讀取前沒有資料,阻塞中...等待資料傳過來再輸出到控制檯...");  
            byte[] buf = new byte[1024];  
            int len = pin.read(buf);  //read阻塞  
            System.out.println("R:讀取資料成功,阻塞解除...");  
            String s= new String(buf,0,len);  
            System.out.println(s);  //將讀取的資料流用字串以字串打印出來  
            pin.close();       
      }  catch(Exception e)  {  
            throw new RuntimeException("R:管道讀取流失敗!");  
      }     
    }  
}

public class Test {
    public static void main(String[] args) throws IOException {
          PipedInputStream pin = new PipedInputStream();  
          PipedOutputStream pout = new PipedOutputStream();  
          pin.connect(pout);  //輸入流與輸出流連線  

          ReadThread readTh   = new ReadThread(pin);  
          WriteThread writeTh = new WriteThread(pout);  
          new Thread(readTh).start();  
          new Thread(writeTh).start();  
    }
}

BufferedInputStream和BufferedOutputStream

FileInputStream和FileOutputStream 在使用時,我們介紹了可以用byte陣列作為資料讀入的快取區,以讀檔案為列,讀取硬碟的速度遠遠低於讀取記憶體的資料,為了減少對硬碟的讀取,通常從檔案中一次讀取一定長度的資料,把資料存入快取中,在寫入的時候也是一次寫入一定長度的資料,這樣可以增加檔案的讀取效率。我們在使用FileInputStream的時候是用byte陣列來做了快取,而BufferedInputStream and BufferedOutputStream已經為我們增加了這個快取功能。

public static void fileBufferTest() {
    File f1 = new File("D:\\in.txt");
    File f2 = new File("D:\\out.txt");
    try {
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(f1));   
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(f2));   
        byte[] data = new byte[1];
        while(bufferedInputStream.read(data)!=-1) {   
            bufferedOutputStream.write(data);   
        }   
        //將緩衝區中的資料全部寫出   
        bufferedOutputStream.flush();   
        bufferedInputStream.close();   
        bufferedOutputStream.close();   
    } catch (Exception e) {
        e.printStackTrace();
    }
}

讀寫物件ObjectInputStream和ObjectOutputStream

public class ObjectStream {
    public static void main(String[] args) {
        ObjectStream.objectStreamTest();
    }

    public static void objectStreamTest() {
        Demo newObject;
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:/123.obj")));
            oos.writeObject(new Demo());
            oos.flush();
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:/123.obj")));
            newObject = (Demo)ois.readObject();
            System.out.println(newObject.num);
            ois.close();
            oos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Demo implements Serializable{
    private static final long serialVersionUID = 1L;
    int num = 30;
}

SequenceInputStream

合併流,將與之相連線的流集組合成一個輸入流並從第一個輸入流開始讀取, 直到到達檔案末尾,接著從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的檔案末尾為止。 合併流的作用是將多個源合併合一個源。可接收列舉類所封閉的多個位元組流物件。

public class Test {
      public static void main(String[] args) {  
             doSequence();  
      }  

      private static void doSequence() {  
         SequenceInputStream sis = null;  // 建立一個合併流的物件  
         BufferedOutputStream bos = null;   // 建立輸出流。  
         try {  
            // 構建流集合 
            Vector<InputStream> vector = new Vector<InputStream>();  
            vector.addElement(new FileInputStream("D:/in.txt"));  
            vector.addElement(new FileInputStream("D:/out.txt"));  
            Enumeration<InputStream> e = vector.elements();  
            sis = new SequenceInputStream(e);  
            bos = new BufferedOutputStream(new FileOutputStream("/Users/zhengchao/cctv/File_OUT.txt"));  
            // 讀寫資料
            byte[] buf = new byte[1024];  
            int len = 0;  
            while ((len = sis.read(buf)) != -1) {  
               bos.write(buf, 0, len);  
               bos.flush();  
            }  
         } catch (Exception e1) {  
            e1.printStackTrace();  
         } finally {  
            try {  
               if (sis != null)  
                  sis.close();  
            } catch (IOException e) {  
               e.printStackTrace();  
            }  
            try {  
               if (bos != null)  
                  bos.close();  
            } catch (IOException e) {  
               e.printStackTrace();  
            }  
         }  
      }  
}

FileReader與FileWriter

public static void fileReaderTest() {
    File f1 = new File("D:\\in.txt");
    File f2 = new File("D:\\out.txt");
    try {
        FileReader fr = new FileReader(f1);
        FileWriter fw = new FileWriter(f2);
        char[] ch = new char[512];
        int len = 0;
        while((len = fr.read(ch)) != -1){
            fw.write(ch, 0, ch.length);
        }
        fw.flush();
        fw.close();
        fr.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

BufferedReader和BufferedWriter

public static void fileBufferReaderWriteTest() {
    File f1 = new File("D:\\in.txt");
    File f2 = new File("D:\\out.txt");
    try {
        BufferedReader br=new BufferedReader(new FileReader(f1));
        BufferedWriter bw=new BufferedWriter(new FileWriter(f2));

        String s=br.readLine();
        while(null!=s) {
            bw.write(s);
            //由於BufferedReader的readLine()是不讀入換行符的,所以寫入換行時須用newLine()方法
            bw.newLine();
            s=br.readLine();
        }
        br.close();
        bw.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

注:在使用過程中應注意將編碼格式設定為UTF-8,否則檔案寫入會亂碼。

以上只是IO流的部分類,用於對IO流相關知識進行查閱,後面再實際的工作中也會根據實際需求不斷新增。
參考自:
https://blog.csdn.net/zhengchao1991/article/details/53033137
https://blog.csdn.net/SilenceOO/article/details/50995062
https://blog.csdn.net/Yue_Chen/article/details/72772445