java IO流的設計是基於裝飾者模式&適配模式,面對IO流龐大的包裝類體系,核心是要抓住其功能所對應的裝飾類。
裝飾模式又名包裝(Wrapper)模式。裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關系的一個替代方案。裝飾模式通過創建一個包裝對象,也就是裝飾,來包裹真實的對象。裝飾模式以對客戶端透明的方式動態地給一個對象附加上更多的責任。換言之,客戶端並不會覺得對象在裝飾前和裝飾后有什么不同。裝飾模式可以在不創造更多子類的情況下,將對象的功能加以擴展。裝飾模式把客戶端的調用委派到被裝飾類。裝飾模式的關鍵在於這種擴展是完全透明的。
裝飾者的角色:
抽象構件角色(Component):給出一個抽象接口,以規范准備接收附加責任的對象。
具體構件角色(Concrete Component):定義將要接收附加責任的類。
裝飾角色(Decorator):持有一個構件(Component)對象的引用,並定義一個與抽象構件接口一致的接口。
具體裝飾角色(Concrete Decorator):負責給構件對象“貼上”附加的責任。
實現案例:
//抽象構件角色 public interface Component { public void doSomething(); }
//具體構件角色 public class ConcreteComponent implements Component { @Override public void doSomething() { System.out.println("功能A"); } }
//裝飾者角色 public class Decorator implements Component { //維護一個抽象構件角色 private Component component; public Decorator(Component component) { this.component = component; } @Override public void doSomething() { component.doSomething(); } }
//具體裝飾者角色 public class ConcreteDecorator1 extends Decorator { public ConcreteDecorator1(Component component) { super(component); } @Override public void doSomething() { super.doSomething(); this.doAnotherThing(); } private void doAnotherThing() { System.out.println("功能B"); } }
以上就是裝飾者模式的一個極簡代碼思路,實際上IO流的裝飾體系也是在對上面思路的一中具體實現。
JAVA-IO流體系:
在IO中,具體構件角色是節點流,裝飾角色是過濾流。
1、繼承自InputStream/OutputStream的流都是用於向程序中輸入/輸出數據,且數據的單位都是字節(byte=8bit),如圖,深色的為節點流,淺色的為過濾流。
2、繼承自Reader/Writer的流都是用於向程序中輸入/輸出數據,且數據的單位都是字符(2byte=16bit),如圖,深色的為節點流,淺色的為過濾流。
從圖中可以看出,InputStream就是裝飾者模式中的超類(Component),ByteArrayInputStream,FileInputStream相當於被裝飾者(ConcreteComponent),這些類都提供了最基本的字節讀取功能。而另外一個和這兩個類是同一級的類FilterInputStream即是裝飾者(Decorator),BufferedInputStream,DataInputStream,PushbackInputStream…這些都是被裝飾者裝飾后形成的成品。為什么可以說:裝飾模式可以在不創造更多子類的情況下,將對象的功能加以擴展,能理解這一點就能很好的掌握裝飾者設計模式的精髓,如果在InputStream這里擴展出FilterInputStream類下面的裝飾類,那么針對FileInputStream和ByteArrayInputStream就都要去實現一次BufferedInputStream了,那么可能就會衍生出BufferedFileInputStream和BufferedByteArrayInputStream這樣的類,如果按照這樣的擴展方式去添加功能,對於添加功能的子類來說簡直是一場噩夢,好在裝飾着模式很好的解決了這個問題,現在我們只需要在過濾流類這里維護一個超類,不論傳入的是什么具體的節點流,那么都只要套一層裝飾,就能對功能方法進行加強。
如果想要對文件輸入流進行緩存加強可以這樣裝飾:
File file = new File ("hello.txt"); BufferedInputStream inBuffered=new BufferedInputStream (new FileInputStream(file));
如果想要對字節數組輸入流進行緩存加強可以這樣裝飾:
byte[] byts="Hello".getBytes(); BufferedInputStream bf=new BufferedInputStream(new ByteArrayInputStream(byts));
那么節點流上的類就可以平行擴展,而裝飾者同樣可以按照功能進行另外一個維度的擴展,調用的時候就可以按需進行組合裝飾,這樣就可以減少了子類還將對象的功能進行擴展,不得不佩服前人在該設計模式上的智慧,理解了這裝飾着模式后,就應該對java中IO流的體系進行梳理:
節點流類型
- 對文件操作的字符流有FileReader/FileWriter,
- 字節流有FileInputStream/FileOutputStream。
過濾流類型
- 緩沖流:緩沖流要“套接”在相應的節點流之上,對讀寫的數據提供了緩沖的功能,提高了讀寫效率,同時增加了一些新的方法。
- 字節緩沖流有BufferedInputStream / BufferedOutputStream,字符緩沖流有BufferedReader / BufferedWriter,字符緩沖流分別提供了讀取和寫入一行的方法ReadLine和NewLine方法。
- 對於輸出的緩沖流,寫出的數據,會先寫入到內存中,再使用flush方法將內存中的數據刷到硬盤。所以,在使用字符緩沖流的時候,一定要先flush,然后再close,避免數據丟失。
- 轉換流:用於字節數據到字符數據之間的轉換。
- 字符流InputStreamReader / OutputStreamWriter。其中,InputStreamReader需要與InputStream“套接”,OutputStreamWriter需要與OutputStream“套接”。
- 數據流:提供了讀寫Java中的基本數據類型的功能。
- DataInputStream和DataOutputStream分別繼承自InputStream和OutputStream,需要“套接”在InputStream和OutputStream類型的節點流之上。
- 對象流:用於直接將對象寫入寫出。
- 流類有ObjectInputStream和ObjectOutputStream,本身這兩個方法沒什么,但是其要寫出的對象有要求,該對象必須實現Serializable接口,來聲明其是可以序列化的。否則,不能用對象流讀寫。(api以及demo在文末)
重點梳理一下:Java中Inputstream/OutputStream與Reader/Writer的區別
Reader/Writer和InputStream/OutputStream分別是I/O庫提供的兩套平行獨立的等級機構,
InputStream、OutputStream是用來處理
8
位元的流,也就是用於讀寫ASCII字符和二進制數據;
Reader、Writer是用來處理
16
位元的流,也就是用於讀寫Unicode編碼的字符。
在
JAVA語言中,
byte
類型是
8
位的,
char
類型是
16
位的,所以在處理中文的時候需要用Reader和Writer。
- 兩種等級機構下,有一道橋梁InputStreamReader、OutputStreamWriter負責進行InputStream到Reader的適配和由OutputStream到Writer的適配。
在Java中,有不同類型的Reader/InputStream輸入流對應於不同的數據源:
FileReader/FileInputStream 用於從文件輸入;
CharArrayReader/ByteArrayInputStream 用於從程序中的字符數組輸入;
StringReader/StringBufferInputStream 用於從程序中的字符串輸入;
PipedReader/PipeInputStream 用於讀取從另一個線程中的 PipedWriter/PipeOutputStream 寫入管道的數據。
相應的也有不同類型的Writer/OutputStream輸出流對應於不同的數據源:FileWriter/FileOutputStream,CharArrayWriter/ByteArrayOutputStream,StringWriter,PipeWriter/PipedOutputStream。
IO流的應用選擇
1、確定選用流對象的步驟
- 確定原始數據的格式
- 確定是輸入還是輸出
- 是否需要轉換流
- 數據的來源(去向)
- 是否需要緩沖
- 是否需要格式化輸出
按照數據格式分
- 二進制格式(只要確定不是純文本格式的),InputStream, OutputStream, 及其所有帶Stream子類
- 純文本格式(比如英文/漢字/或其他編碼文字):Reader, Writer, 及其相關子類
按照輸入輸出分
- 輸入:Reader, InputStream,及其相關子類
- 輸出:Writer,OutputStream,及其相關子類
按緩沖功能分
- 要緩沖:BufferedInputStream, BufferedOuputStream, BuffereaReader, BufferedWriter
按照格式化輸出
- 需要格式化輸出:PrintStream(輸出字節),PrintWriter(輸出字符)
特殊需求
- 從Stream轉化為Reader,Writer:InputStreamReader,OutputStreamWriter
- 對象輸入輸出流:ObjectInputStream,ObjectOutputStream
- 進程間通信:PipeInputStream,PipeOutputStream,PipeReader,PipeWriter
- 合並輸入:SequenceInputStream
- 更特殊的需要:PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader
//對象流案例 public class Demo3 { public static void main(String[] args) throws IOException, ClassNotFoundException { Cat cat = new Cat("tom", 3); FileOutputStream fos = new FileOutputStream(new File("c:\\Cat.txt")); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(cat); System.out.println(cat); oos.close(); // 反序列化 FileInputStream fis = new FileInputStream(new File("c:\\Cat.txt")); ObjectInputStream ois = new ObjectInputStream(fis); Object readObject = ois.readObject(); Cat cat2 = (Cat) readObject; System.out.println(cat2); fis.close(); } class Cat implements Serializable { public String name; public int age; public Cat() { } public Cat(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Cat [name=" + name + ", age=" + age + "]"; } }
//DataInputStream 基本數據類型和String //操作基本數據類型的方法: int readInt()://一次讀取四個字節,並將其轉成int值。 boolean readBoolean()://一次讀取一個字節。 short readShort(); long readLong(); //剩下的數據類型一樣。 String readUTF()://按照utf-8修改版讀取字符。注意,它只能讀writeUTF()//寫入的字符數據。 DataOutputStream DataOutputStream(OutputStream): //操作基本數據類型的方法: writeInt(int)://一次寫入四個字節。 //注意和write(int)不同。write(int)只將該整數的最低一個8位寫入。剩余三個8位丟棄。 writeBoolean(boolean); writeShort(short); writeLong(long); //剩下是數據類型也也一樣。 writeUTF(String)://按照utf-8修改版將字符數據進行存儲。只能通過readUTF讀取。
轉換流:
InputStreamReader:字節到字符的橋梁。
OutputStreamWriter:字符到字節的橋梁。
//從字節流中讀取字符信息 BufferedReader bf=new InputStreamReader(new FileInputStream("src")); //將字符信息用指定字節編碼寫出 OutputStreamWriter bw=new OutputStreamWriter(new FileOutputStream("target"),"utf-8"); bw.write("Hello");
public class TestIo { public class Demo4 { public static void main(String[] args) throws IOException { File file = new File("c:\\a.txt"); File fileGBK = new File("c:\\gbk.txt"); File fileUTF = new File("c:\\utf.txt"); // 寫入 // 使用系統默認碼表寫入 testWriteFile(file); // 使用gbk編碼向gbk文件寫入信息 testWriteFile(fileGBK, "gbk"); // 使用utf-8向utf-8文件中寫入信息 testWriteFile(fileUTF, "utf-8"); // 讀取 // 默認編碼 testReadFile(file); // 傳入gbk編碼文件,使用gbk解碼 testReadFile(fileGBK, "gbk"); // 傳入utf-8文件,使用utf-8解碼 testReadFile(fileUTF, "utf-8"); } // 使用系統碼表將信息寫入到文件中 private static void testWriteFile(File file) throws IOException { FileOutputStream fos = new FileOutputStream(file); OutputStreamWriter ops = new OutputStreamWriter(fos); ops.write("中國"); ops.close(); } // 使用指定碼表,將信息寫入到文件中 private static void testWriteFile(File file, String encod) throws IOException { FileOutputStream fos = new FileOutputStream(file); OutputStreamWriter ops = new OutputStreamWriter(fos, encod); ops.write("中國"); ops.close(); } // 該方法中nputStreamReader使用系統默認編碼讀取文件. private static void testReadFile(File file) throws IOException { FileInputStream fis = new FileInputStream(file); InputStreamReader ins = new InputStreamReader(fis); int len = 0; while ((len = ins.read()) != -1) { System.out.print((char) len); } ins.close(); } // 該方法適合用指定編碼讀取文件 private static void testReadFile(File file, String encod) throws IOException { FileInputStream fis = new FileInputStream(file); InputStreamReader ins = new InputStreamReader(fis, encod); int len = 0; while ((len = ins.read()) != -1) { System.out.print((char) len); } ins.close(); } }