程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> java io學習(十九) PipedReader和PipedWriter

java io學習(十九) PipedReader和PipedWriter

編輯:關於JAVA

本章,我們學習PipedReader和PipedWriter。它們和“PipedInputStream和PipedOutputStream”一樣,都可以用於管道通信。

PipedWriter 是字符管道輸出流,它繼承於Writer。

PipedReader 是字符管道輸入流,它繼承於Writer。

PipedWriter和PipedReader的作用是可以通過管道進行線程間的通訊。在使用管道通信時,必須將PipedWriter和PipedReader配套使用。

PipedWriter和PipedReader源碼分析

1. PipedWriter 源碼(基於jdk1.7.40)

package java.io;
     
public class PipedWriter extends Writer {
     
    // 與PipedWriter通信的PipedReader對象
    private PipedReader sink;
     
    // PipedWriter的關閉標記
    private boolean closed = false;
     
    // 構造函數,指定配對的PipedReader
    public PipedWriter(PipedReader snk)  throws IOException {
        connect(snk);
    }
     
    // 構造函數
    public PipedWriter() {
    }
     
    // 將“PipedWriter” 和 “PipedReader”連接。
    public synchronized void connect(PipedReader snk) throws IOException {
        if (snk == null) {
            throw new NullPointerException();
        } else if (sink != null || snk.connected) {
            throw new IOException("Already connected");
        } else if (snk.closedByReader || closed) {
            throw new IOException("Pipe closed");
        }
     
        sink = snk;
        snk.in = -1;
        snk.out = 0;
        // 設置“PipedReader”和“PipedWriter”為已連接狀態
        // connected是PipedReader中定義的,用於表示“PipedReader和PipedWriter”是否已經連接
        snk.connected = true;
    }
     
    // 將一個字符c寫入“PipedWriter”中。
    // 將c寫入“PipedWriter”之後,它會將c傳輸給“PipedReader”
    public void write(int c)  throws IOException {
        if (sink == null) {
            throw new IOException("Pipe not connected");
        }
        sink.receive(c);
    }
     
    // 將字符數組b寫入“PipedWriter”中。
    // 將數組b寫入“PipedWriter”之後,它會將其傳輸給“PipedReader”
    public void write(char cbuf[], int off, int len) throws IOException {
        if (sink == null) {
            throw new IOException("Pipe not connected");
        } else if ((off | len | (off + len) | (cbuf.length - (off + len))) < 0) {
            throw new IndexOutOfBoundsException();
        }
        sink.receive(cbuf, off, len);
    }
     
    // 清空“PipedWriter”。
    // 這裡會調用“PipedReader”的notifyAll();
    // 目的是讓“PipedReader”放棄對當前資源的占有,讓其它的等待線程(等待讀取PipedWriter的線程)讀取“PipedWriter”的值。
    public synchronized void flush() throws IOException {
        if (sink != null) {
            if (sink.closedByReader || closed) {
                throw new IOException("Pipe closed");
            }
            synchronized (sink) {
                sink.notifyAll();
            }
        }
    }
     
    // 關閉“PipedWriter”。
    // 關閉之後,會調用receivedLast()通知“PipedReader”它已經關閉。
    public void close()  throws IOException {
        closed = true;
        if (sink != null) {
            sink.receivedLast();
        }
    }
}

2. PipedReader 源碼(基於jdk1.7.40)

package java.io;
     
public class PipedReader extends Reader {
    // “PipedWriter”是否關閉的標記
    boolean closedByWriter = false;
    // “PipedReader”是否關閉的標記
    boolean closedByReader = false;
    // “PipedReader”與“PipedWriter”是否連接的標記
    // 它在PipedWriter的connect()連接函數中被設置為true
    boolean connected = false;
     
    Thread readSide;    // 讀取“管道”數據的線程
    Thread writeSide;    // 向“管道”寫入數據的線程
     
    // “管道”的默認大小
    private static final int DEFAULT_PIPE_SIZE = 1024;
     
    // 緩沖區
    char buffer[];
     
    //下一個寫入字符的位置。in==out代表滿,說明“寫入的數據”全部被讀取了。
    int in = -1;
    //下一個讀取字符的位置。in==out代表滿,說明“寫入的數據”全部被讀取了。
    int out = 0;
     
    // 構造函數:指定與“PipedReader”關聯的“PipedWriter”
    public PipedReader(PipedWriter src) throws IOException {
        this(src, DEFAULT_PIPE_SIZE);
    }
     
    // 構造函數:指定與“PipedReader”關聯的“PipedWriter”,以及“緩沖區大小”
    public PipedReader(PipedWriter src, int pipeSize) throws IOException {
        initPipe(pipeSize);
        connect(src);
    }
     
    // 構造函數:默認緩沖區大小是1024字符
    public PipedReader() {
        initPipe(DEFAULT_PIPE_SIZE);
    }
     
    // 構造函數:指定緩沖區大小是pipeSize
    public PipedReader(int pipeSize) {
        initPipe(pipeSize);
    }
     
    // 初始化“管道”:新建緩沖區大小
    private void initPipe(int pipeSize) {
        if (pipeSize <= 0) {
            throw new IllegalArgumentException("Pipe size <= 0");
        }
        buffer = new char[pipeSize];
    }
     
    // 將“PipedReader”和“PipedWriter”綁定。
    // 實際上,這裡調用的是PipedWriter的connect()函數
    public void connect(PipedWriter src) throws IOException {
        src.connect(this);
    }
     
    // 接收int類型的數據b。
    // 它只會在PipedWriter的write(int b)中會被調用
    synchronized void receive(int c) throws IOException {
        // 檢查管道狀態
        if (!connected) {
            throw new IOException("Pipe not connected");
        } else if (closedByWriter || closedByReader) {
            throw new IOException("Pipe closed");
        } else if (readSide != null && !readSide.isAlive()) {
            throw new IOException("Read end dead");
        }
     
        // 獲取“寫入管道”的線程
        writeSide = Thread.currentThread();
        // 如果“管道中被讀取的數據,等於寫入管道的數據”時,
        // 則每隔1000ms檢查“管道狀態”,並喚醒管道操作:若有“讀取管道數據線程被阻塞”,則喚醒該線程。
        while (in == out) {
            if ((readSide != null) && !readSide.isAlive()) {
                throw new IOException("Pipe broken");
            }
            /* full: kick any waiting readers */
            notifyAll();
            try {
                wait(1000);
            } catch (InterruptedException ex) {
                throw new java.io.InterruptedIOException();
            }
        }
        if (in < 0) {
            in = 0;
            out = 0;
        }
        buffer[in++] = (char) c;
        if (in >= buffer.length) {
            in = 0;
        }
    }
     
    // 接收字符數組b。
    synchronized void receive(char c[], int off, int len)  throws IOException {
        while (--len >= 0) {
            receive(c[off++]);
        }
    }
     
    // 當PipedWriter被關閉時,被調用
    synchronized void receivedLast() {
        closedByWriter = true;
        notifyAll();
    }
     
    // 從管道(的緩沖)中讀取一個字符,並將其轉換成int類型
	// 查看本欄目
			
		
		

示例

下面,我們看看多線程中通過PipedWriter和PipedReader通信的例子。例子中包括3個類:Receiver.java, Sender.java 和 PipeTest.java

Receiver.java的代碼如下:

import java.io.IOException;   
        
import java.io.PipedReader;   
        
@SuppressWarnings("all")   
/**  
 * 接收者線程  
 */
public class Receiver extends Thread {   
            
    // 管道輸入流對象。
    // 它和“管道輸出流(PipedWriter)”對象綁定,
    // 從而可以接收“管道輸出流”的數據,再讓用戶讀取。
    private PipedReader in = new PipedReader();   
        
    // 獲得“管道輸入流對象”
    public PipedReader getReader(){   
        return in;   
    }   
            
    @Override
    public void run(){   
        readMessageOnce() ;
        //readMessageContinued() ;
    }
     
    // 從“管道輸入流”中讀取1次數據
    public void readMessageOnce(){   
        // 雖然buf的大小是2048個字符,但最多只會從“管道輸入流”中讀取1024個字符。
        // 因為,“管道輸入流”的緩沖區大小默認只有1024個字符。
        char[] buf = new char[2048];   
        try {   
            int len = in.read(buf);   
            System.out.println(new String(buf,0,len));   
            in.close();   
        } catch (IOException e) {   
            e.printStackTrace();   
        }   
    }
     
    // 從“管道輸入流”讀取>1024個字符時,就停止讀取
    public void readMessageContinued(){
        int total=0;
        while(true) {
            char[] buf = new char[1024];
            try {
                int len = in.read(buf);
                total += len;
                System.out.println(new String(buf,0,len));
                // 若讀取的字符總數>1024,則退出循環。
                if (total > 1024)
                    break;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
     
        try {
            in.close(); 
        } catch (IOException e) {   
            e.printStackTrace();   
        }   
    }   
}

Sender.java的代碼如下:

import java.io.IOException;   
        
import java.io.PipedWriter;   
@SuppressWarnings("all")
/**  
 * 發送者線程  
 */
public class Sender extends Thread {   
            
    // 管道輸出流對象。
    // 它和“管道輸入流(PipedReader)”對象綁定,
    // 從而可以將數據發送給“管道輸入流”的數據,然後用戶可以從“管道輸入流”讀取數據。
    private PipedWriter out = new PipedWriter();
     
    // 獲得“管道輸出流”對象
    public PipedWriter getWriter(){
        return out;
    }   
     
    @Override
    public void run(){   
        writeShortMessage();
        //writeLongMessage();
    }   
     
    // 向“管道輸出流”中寫入一則較簡短的消息:"this is a short message" 
    private void writeShortMessage() {
        String strInfo = "this is a short message" ;
        try {
            out.write(strInfo.toCharArray());
            out.close();   
        } catch (IOException e) {   
            e.printStackTrace();   
        }   
    }
    // 向“管道輸出流”中寫入一則較長的消息
    private void writeLongMessage() {
        StringBuilder sb = new StringBuilder();
        // 通過for循環寫入1020個字符
        for (int i=0; i<102; i++)
            sb.append("0123456789");
        // 再寫入26個字符。
        sb.append("abcdefghijklmnopqrstuvwxyz");
        // str的總長度是1020+26=1046個字符
        String str = sb.toString();
        try {
            // 將1046個字符寫入到“管道輸出流”中
            out.write(str);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

PipeTest.java的代碼如下:

import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.IOException;
     
@SuppressWarnings("all")   
/**  
 * 管道輸入流和管道輸出流的交互程序
 */
public class PipeTest {   
        
    public static void main(String[] args) {   
        Sender t1 = new Sender();   
                
        Receiver t2 = new Receiver();   
                
        PipedWriter out = t1.getWriter();   
      
        PipedReader in = t2.getReader();   
     
        try {   
            //管道連接。下面2句話的本質是一樣。
            //out.connect(in);   
            in.connect(out);   
                    
            /**  
             * Thread類的START方法:  
             * 使該線程開始執行;Java 虛擬機調用該線程的 run 方法。   
             * 結果是兩個線程並發地運行;當前線程(從調用返回給 start 方法)和另一個線程(執行其 run 方法)。   
             * 多次啟動一個線程是非法的。特別是當線程已經結束執行後,不能再重新啟動。   
             */
            t1.start();
            t2.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

this is a short message

結果說明:

(01) in.connect(out);

   它的作用是將“管道輸入流”和“管道輸出流”關聯起來。查看PipedWriter.java和PipedReader.java中connect()的源碼;我們知道 out.connect(in); 等價於 in.connect(out);

(02)

t1.start(); // 啟動“Sender”線程

t2.start(); // 啟動“Receiver”線程

先查看Sender.java的源碼,線程啟動後執行run()函數;在Sender.java的run()中,調用writeShortMessage();

writeShortMessage();的作用就是向“管道輸出流”中寫入數據"this is a short message" ;這條數據會被“管道輸入流”接收到。下面看看這是如何實現的。

先看write(char char的源碼。PipedWriter.java繼承於Writer.java;Writer.java中write(char c[])的源碼如下:

public void write(char cbuf[]) throws IOException {

write(cbuf, 0, cbuf.length);

}

實際上write(char c[])是調用的PipedWriter.java中的write(char c[], int off, int len)函數。查看write(char c[], int off, int len)的源碼,我們發現:它會調用 sink.receive(cbuf, off, len); 進一步查看receive(char c[], int off, int len)的定義,我們知道sink.receive(cbuf, off, len)的作用就是:將“管道輸出流”中的數據保存到“管道輸入流”的緩沖中。而“管道輸入流”的緩沖區buffer的默認大小是1024個字符。

至此,我們知道:t1.start()啟動Sender線程,而Sender線程會將數據"this is a short message"寫入到“管道輸出流”;而“管道輸出流”又會將該數據傳輸給“管道輸入流”,即而保存在“管道輸入流”的緩沖中。

接下來,我們看看“用戶如何從‘管道輸入流’的緩沖中讀取數據”。這實際上就是Receiver線程的動作。

t2.start() 會啟動Receiver線程,從而執行Receiver.java的run()函數。查看Receiver.java的源碼,我們知道run()調用了readMessageOnce()。

而readMessageOnce()就是調用in.read(buf)從“管道輸入流in”中讀取數據,並保存到buf中。

通過上面的分析,我們已經知道“管道輸入流in”的緩沖中的數據是"this is a short message";因此,buf的數據就是"this is a short message"。

為了加深對管道的理解。我們接著進行下面兩個小試驗。

試驗一:修改Sender.java

public void run(){

writeShortMessage();

//writeLongMessage();

}

修改為

public void run(){

//writeShortMessage();

writeLongMessage();

}

運行程序。運行結果如下:

從中,我們看出,程序運行出錯!拋出異常 java.io.IOException: Pipe closed

為什麼會這樣呢?

我分析一下程序流程。

(01) 在PipeTest中,通過in.connect(out)將輸入和輸出管道連接起來;然後,啟動兩個線程。t1.start()啟動了線程Sender,t2.start()啟動了線程Receiver。

(02) Sender線程啟動後,通過writeLongMessage()寫入數據到“輸出管道”,out.write(str.toCharArray())共寫入了1046個字符。而根據PipedWriter的源碼,PipedWriter的write()函數會調用PipedReader的receive()函數。而觀察PipedReader的receive()函數,我們知道,PipedReader會將接受的數據存儲緩沖區。仔細觀察receive()函數,有如下代碼:

while (in == out) {
    if ((readSide != null) && !readSide.isAlive()) {
        throw new IOException("Pipe broken");
    }
    /* full: kick any waiting readers */
    notifyAll();
    try {
        wait(1000);
    } catch (InterruptedException ex) {
        throw new java.io.InterruptedIOException();
    }
}

而in和out的初始值分別是in=-1, out=0;結合上面的while(in==out)。我們知道,它的含義就是,每往管道中寫入一個字符,就達到了in==out這個條件。然後,就調用notifyAll(),喚醒“讀取管道的線程”。

也就是,每往管道中寫入一個字符,都會阻塞式的等待其它線程讀取。

然而,PipedReader的緩沖區的默認大小是1024!但是,此時要寫入的數據卻有1046!所以,一次性最多只能寫入1024個字符。

(03) Receiver線程啟動後,會調用readMessageOnce()讀取管道輸入流。讀取1024個字符會,會調用close()關閉,管道。

由(02)和(03)的分析可知,Sender要往管道寫入1046個字符。其中,前1024個字符(緩沖區容量是1024)能正常寫入,並且每寫入一個就讀取一個。當寫入1025個字符時,依然是依次的調用PipedWriter.java中的write();然後,write()中調用PipedReader.java中的receive();在PipedReader.java中,最終又會調用到receive(int c)函數。 而此時,管道輸入流已經被關閉,也就是closedByReader為true,所以拋出throw new IOException("Pipe closed")。

我們對“試驗一”繼續進行修改,解決該問題。

試驗二: 在“試驗一”的基礎上繼續修改Receiver.java

public void run(){

readMessageOnce() ;

//readMessageContinued() ;

}

修改為

public void run(){

//readMessageOnce() ;

readMessageContinued() ;

}

此時,程序能正常運行。運行結果為:

01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789

01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789

012345678901234567890123456789abcd

efghijklmnopqrstuvwxyz

來源:http://www.cnblogs.com/skywang12345/p/io_20.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved