《Java面向對象編程》學習筆記16—Java I/O系統

原文出處:http://blog.csdn.net/gnuhpc/archive/2009/10/22/4713381.aspx

1.基本概念和基本情況:

a.流:一組有序的數據序列。

b.字節流:數據流中最小的數據單元是字節。

c.字符流:數據流中最小的數據單元是字符。

d.java.io.InputStream和java.io.OutputStream分別表示字節輸入流和字節輸出流。都是抽象類,不能被實例化。所有的字節操作流都是這兩個類的直接或間接子類。read()讀取的是一個8位字節,write()寫入的是一個8位字節。

e.java.io.Reader和java.io.Writer分別表示字符輸入流和字符輸出流。都是抽象類,不能被實例化。所有的字符操作流都是這兩個類的直接或間接子類。

2.字節輸入流:

image_thumb2

1)字節數組輸入流:ByteArrayInputStream類

從內存中的字節數組中讀取數據,其數據源是一個字節數組。該類本身採用了適配器設計模式,將字節數組類型轉化爲輸入流,完成對數組的讀操作,讀到的每一個字節類型的元素都會自動轉換爲int。

使用如下:

import java.io.ByteArrayInputStream; 
import java.io.IOException;

public class Test { 
    public static void main(String[] args){ 
        byte buff[]= new byte[]{2,15,67,-1,-9,9}; 
        ByteArrayInputStream inputStream = new ByteArrayInputStream(buff,1,4); 
        int data = inputStream.read(); 
        while (data!=-1) { 
            System.out.println(data + " "); 
            data = inputStream.read(); 
        } 
        try { 
            inputStream.close(); 
        } catch (IOException e) { 
            // TODO Auto-generated catch block 
            e.printStackTrace(); 
        } 
   } 
}

這裏的值會在前邊補0而導致變化,但是請注意在byte類型賦值給int類型時,取值不會變。

2)文件輸入流:FileInputStream類

從文件中讀取數據流。注意系統的默認編碼格式。

3)字符串輸入流:StringBufferInputStream類。(已被StringReader類代替而廢棄)

從一個字符串中讀取。本身採用了適配器設計模式。

4)管道輸入流PipedInputStream類

通常用於線程間通信,A執行管道輸入流的read方法,暫時沒有數據的時候這個線程就會被阻塞,只有當線程B向管道輸入流寫了新的數據後A纔會執行。

5)順序輸入流:SequenceInputStream類

用於將幾個輸入流串聯在一起,合併爲一個輸入流。當類通過這個類來讀取數據時,它會依次(以輸入流加入爲序) 從所有被串聯的輸入流中讀取數據。關閉輸入流不需要先依次關閉各個子輸入流。

6)過濾輸入流:FilterInputStream類

這是裝飾器,它本身繼承了InputStream類,還可以用來修飾其他的輸入流類。其構造方法爲protected訪問級別,因此外部程序不能創建這個類本身的實例。

關於裝飾器模式,在這摘錄一個網友(江蘇 無錫 繆小東 )的文字,我把一些調侃的話刪掉了,留其本質內容:

“三明治”的例子:三明治必不可少的是兩塊麪包片,然後可以在夾層里加上蔬菜、沙拉、鹹肉等等,外面可以塗上奶油之類的。假如現在你要爲一個三明治小店構造一個程序,其中要設計各種三明治的對象。可能你已經創建了一個簡單的Sandwich對象,現在要產生帶蔬菜的就是繼承原有的Sandwich添加一個蔬菜的成員變量,看起來很“正點”的做法,以後我還要帶鹹肉的、帶奶油的、帶蔬菜的又分爲帶青菜的、帶芹菜的、生菜的……還是一個一個繼承是吧!假如我們還需要即帶蔬菜又帶其它肉類,設置我們還要求這些添加成分的任意組合,那你就慢慢繼承吧!下面我們就使用裝飾器模式來設計這個庫吧!下圖是我們的設計圖:

下面是以上各個類的意義:

1. Ingredient(成分):所有類的父類,包括它們共有的方法,一般爲抽象類且方法都有默認的實現,也可以爲接口。它有Bread和Decorator兩個子類。這種實際不存在的,系統需要的抽象類僅僅表示一個概念,圖中用紅色表示。

2. Bread(麪包):就是我們三明治中必須的兩片面包。它是系統中最基本的元素,也是被裝飾的元素,和IO中的媒質流(原始流)一個意義。在裝飾器模式中屬於一類角色,所以其顏色爲紫色。

3. Decorator(裝飾器):所有其它成分的父類,這些成分可以是豬肉、羊肉、青菜、芹菜。這也是一個實際不存在的類,僅僅表示一個概念,即具有裝飾功能的所有對象的父類。圖中用藍色表示。

4. Pork(豬肉):具體的一個成分,不過它作爲裝飾成分和麪包搭配。

5. Mutton(羊肉):同上。

6. Celery(芹菜):同上。

7. Greengrocery(青菜):同上。

總結一下裝飾器模式中的四種角色:1.被裝飾對象(Bread);2.裝飾對象(四種);3.裝飾器(Decorator);4.公共接口或抽象類(Ingredient)。其中1和2是系統或者實際存在的,3和4是實現裝飾功能需要的抽象類。

寫段代碼體會其威力吧!(程序很簡單,但是實現的方法中可以假如如何你需要的方法,意境慢慢體會吧!)

//Ingredient.java

public abstract class Ingredient {

public abstract String getDescription();

public abstract double getCost();

public void printDescription(){

System.out.println(" Name "+ this.getDescription());

System.out.println(" Price RMB "+ this.getCost());

}

}

所有成分的父類,抽象類有一個描述自己的方法和一個得到價格的方法,以及一個打印自身描述和價格的方法(該方法與上面兩個方法構成模板方法哦!)

//Bread.java

public class Bread extends Ingredient {

private String description ;

public Bread(String desc){

this.description=desc ;

}

public String getDescription(){

return description ;

}

public double getCost(){

return 2.48 ;

}

}

麪包類,因爲它是一個具體的成分,因此實現父類的所有的抽象方法。描述可以通過構造器傳入,也可以通過set方法傳入。同樣價格也是一樣的,我就很簡單地返回了。

//Decorator.java

public abstract class Decorator extends Ingredient {

Ingredient ingredient ;

public Decorator(Ingredient igd){

this.ingredient = igd;

}

public abstract String getDescription();

public abstract double getCost();

}

裝飾器對象,所有具體裝飾器對象父類。它最經典的特徵就是:1.必須有一個它自己的父類爲自己的成員變量;2.必須繼承公共父類。 這是因爲裝飾器也是一種成分,只不過是那些具體具有裝飾功能的成分的公共抽象罷了。在我們的例子中就是有一個Ingredient作爲其成員變量。Decorator繼承了Ingredient類。

//Pork.java

public class Pork extends Decorator{

public Pork(Ingredient igd){

super(igd);

}

public String getDescription(){

String base = ingredient.getDescription();

return base +"/n"+"Decrocated with Pork !";

}

public double getCost(){

double basePrice = ingredient.getCost();

double porkPrice = 1.8;

return basePrice + porkPrice ;

}

}

具體的豬肉成分,同時也是一個具體的裝飾器,因此它繼承了Decorator類 。豬肉裝飾器裝飾可以所有的其他對象,因此通過構造器傳入一個Ingredient的實例,程序中調用了父類的構造方法,主要父類實現了這樣的邏輯關係。同樣因爲方法是具體的成分,所以getDescription得到了實現,不過由於它是具有裝飾功能的成分,因此它的描述包含了被裝飾成分的描述和自身的描述。價格也是一樣的。價格放回的格式被裝飾成分與豬肉成分的種價格哦!

從上面兩個方法中我們可以看出,豬肉裝飾器的功能得到了增強,它不僅僅有自己的描述和價格,還包含被裝飾成分的描述和價格。主要是因爲被裝飾成分是它的成員變量,因此可以任意調用它們的方法,同時可以增加自己的額外的共同,這樣就增強了原來成分的功能。

//Mutton.java

public class Mutton extends Decorator{

public Mutton(Ingredient igd){

super(igd);

}

public String getDescription(){

String base = ingredient.getDescription();

return base +"/n"+"Decrocated with Mutton !";

}

public double getCost(){

double basePrice = ingredient.getCost();

double muttonPrice = 2.3;

return basePrice + muttonPrice ;

}

}

羊肉的包裝器。

//Celery.java

public class Celery extends Decorator{

public Celery(Ingredient igd){

super(igd);

}

public String getDescription(){

String base = ingredient.getDescription();

return base +"/n"+"Decrocated with Celery !";

}

public double getCost(){

double basePrice = ingredient.getCost();

double celeryPrice =0.6;

return basePrice + celeryPrice ;

}

}

芹菜的包裝器。

//GreenGrocery.java

public class GreenGrocery extends Decorator{

public GreenGrocery (Ingredient igd){

super(igd);

}

public String getDescription(){

String base = ingredient.getDescription();

return base +"/n"+"Decrocated with GreenGrocery !";

}

public double getCost(){

double basePrice = ingredient.getCost();

double greenGroceryPrice = 0.4;

return basePrice + greenGroceryPrice ;

}

}

青菜的包裝器。

下面我們就領略裝飾器模式的神奇了!我們有一個測試類,其中建立夾羊肉的三明治、全蔬菜的三明治、全葷的三明治。 
public class DecoratorTest{

public static void main(String[] args){

Ingredient compound = new Mutton(new Celery(new Bread("Master24's Bread")));

compound.printDescription();

compound = new Celery(new GreenGrocery(new Bread("Bread with milk")));

compound.printDescription();

compound = new Mutton(new Pork(new Bread("Bread with cheese")));

compound.printDescription();

}

}

以上就是一個簡單的裝飾器類。

a.DataInputStream類

實現了DataInput接口,用於讀取基本類型數據。如int,long,float,double,boolean。其中的readUTF()方法還能讀取UTF-8編碼的字符串。其應該與DataOutputStream配套使用。也就是說,用DataInputStream讀取由DataOutputStream寫出的數據,才能保證獲得正確的數據。

b.LineNumberInputStream類:已被廢棄

c.BufferedInputStream類

覆蓋了被裝飾的輸入流的讀數據行爲,利用buffer來提供效率。一般在讀取數據文件或者鍵盤輸入時都使用其進行裝飾。

d.PushbackInputStream類

很少用到。

3.字節輸出流

image_thumb4

1)字節數組輸出流:ByteArrayOutputStream類

向內存中的字節數組寫數據,使用toByteArray方法獲得字節數組。

2)文件輸出流:FileOutputStream類

向文件中寫數據。注意默認下使用則是覆蓋文件,必須使用FileOutputStream(String name,boolean append),在後邊設置爲true則是在追加。

3)過濾輸出流:FilterOutputStream類

和過濾輸入流的特性一樣。

a)DataOutputStream類:

用於向輸出流寫基本數據類型。

b)BufferedOutputStream類:

緩衝區提高效率。在超過緩衝區時會將緩衝區的內的數據寫入輸出流,若沒有超過則不會自動寫入,否則使用flush()方法刷新,或者關閉輸出流。

c)PrintStream類:

採用本地OS默認的字符編碼輸出格式化數據。每一個Print()方法都與一個println()方法對應。使用checkError()方法判斷寫數據是否成功。它也帶有緩衝區,不過用戶可以自己設置刷新規則(見其構造函數的一個重載)。

4.字符輸入流

image_thumb8

注意BufferedReader不是FilterReader的子類。

1)字符數組輸入流:CharArrayReader類

從內存中的字符數組中讀取字符。

2)字符串輸入流:StringReader類

數據源是字符串。

3)InputStreamReader類:

image_thumb10

主要是解決Unicode字符編碼和用戶指定編碼的變換問題。爲了提高效率則可以使用BufferedReader來修飾。它有個子類是FileReader類,但是其只能按照本地平臺的字符編碼來讀取數據,用戶不能指定編碼類型。

4)BufferedReader類:

帶有緩衝區的字符輸入流。

5.字符輸出流

image_thumb12

1)字符數組輸出流:CharArrayWriter類

將內存中的字符中寫入字符數組。

2)BufferedWriter類:帶有緩衝。

3)OutputStreamWriter類:

爲了提高效率則可以使用BufferedWriter來修飾。它有個子類是FileWriter類,但是其只能按照本地平臺的字符編碼來寫入數據,用戶不能指定編碼類型。

4)PrintWriter類:能輸出格式化的數據。在輸出字符數據的場合,應該優先考慮用PrintWriter。

6.標準IO

三個標準輸入輸出流:

System.in:InputStream類,代表標準輸入流,默認爲鍵盤。常用套路是InputStreamReader適配器把System.in轉換爲Reader類型,再用BufferedReader裝飾它。

System.out:PrintStream類,代表標準輸出流,默認爲控制檯。通常轉化爲PrintWriter類型。

System.err:PrintStream類,代表標準錯誤輸出流,默認爲控制檯。

重定向使用System類中的,setIN(),setOut(),setErr()方法。

7.隨機訪問文件類:RandomAccessFile

可以從任意位置對文件進行讀寫。實現了DataInput和DataOutput接口,可以讀寫格式化的數據。

8.新IO庫的部分介紹

java.nio包中,目的在於提高IO操作效率。緩衝器Buffer類:減少了實際物理讀寫次數,減少動態分配和回收內存區域的次數。這個類可以使得程序直接控制和運用緩衝區。抽象類,有8個具體的緩衝區類,都有能返回自身實例的靜態工廠方法。

    image_thumb15

    緩衝區的幾個屬性(也提供了改變這三個屬性的方法):

容量:緩衝區的大小。

極限:緩衝區的當前終點。這個點即爲實際的數據區的終點。

位置:緩衝區的下一個讀寫單元。

image_thumb14

Channel是用來連接緩衝區與數據源的。通道在創建時被打開,一旦關閉就不能再打開了。

有兩個常用方法:一個是flip(),確保後續操作只針對在緩衝區中的當前數據,而clear()方法則把緩衝區的極限設爲容量值,爲後續向緩衝區填入更多的數據做準備。

2)字符編碼Charset類

其中的每個實例都代表特定的字符編碼類型。

3)FileChannel讀寫文件

放一個例子吧,從書上摘的:

import java.io.FileInputStream; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.RandomAccessFile; 
import java.nio.ByteBuffer; 
import java.nio.channels.FileChannel; 
import java.nio.charset.Charset;

public class Test{ 
    public static void main(String[] args) throws IOException{ 
        final int SIZE = 1024; 
        /*向文件中寫數據*/ 
        FileChannel fcChannel = new FileOutputStream("c://test.txt").getChannel(); 
        fcChannel.write(ByteBuffer.wrap("你好,".getBytes()));//靜態方法wrap爲包裝爲一個ByteBuffer對象 
        fcChannel.close(); 
        /*向文件末尾添加數據*/ 
        fcChannel = new RandomAccessFile("c://test.txt","rw").getChannel(); 
        fcChannel.position(fcChannel.size());//定位到文件尾 
        fcChannel.write(ByteBuffer.wrap("世界!".getBytes())); 
        fcChannel.close(); 
        /*讀數據*/ 
        fcChannel = new FileInputStream("c://test.txt").getChannel(); 
        ByteBuffer buffer = ByteBuffer.allocate(SIZE);//創建ByteBuffer對象 
        fcChannel.read(buffer);//將文件中的數據讀入緩衝區中 
        buffer.flip();//將極限設爲當前位置值,再把position設爲0,這使得接下來的cs.decode(buff)方法僅僅操作剛剛寫入緩衝區的數據。 
        Charset cs = Charset.defaultCharset();//獲得本地OS的字符編碼 
        System.out.println(cs.decode(buffer));//轉換爲Unicode字符編碼 
        fcChannel.close(); 
    } 
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章