Java/c++ IO 實例詳解 (字節流 字符流 Inputstream outInputstream Bufferedstream 源碼分析)

1 Java 字節流(byte),字符流(char,string)區別?

什麼是流:IO操作就是流。比如,標準輸入輸出,讀寫文件,內存賦值。
字節,字符區別:byte 1個字節,java char is 兩個字節. c++ char is 1個字節
應用場景:字符流用於是文本,字節流用於所有場景。
常用字節流:ByteArrayInputStream,ObjectInputStream,FileInputStream,
FilterInputStream(BufferedInputStream,DataInputStream)。output同樣。
常用字符流:CharArrayReader,BufferedRead,FileReader .writer同樣.
轉換流:InputStreamReader,OutputStreamWriter.
關鍵字:Reader/Writer 是字符流,Input/output是字節流 。既有input(output)又有reader(writer)是轉化;Buffer是對流的緩衝,增加效率.

2 Java IO 導圖

字節流導圖

3 Java 各種場景使用實例 (讀String,Socket,讀文件,標準IO)

3.1 文件

3.1.1 字節流(字節緩衝流)

public static void copyFile(File sourceFile, File targetFile)
        throws IOException {
    // 新建文件輸入流並對它進行緩衝
    FileInputStream input = new FileInputStream(sourceFile);
    //注意這僅僅是打開流,不是讀寫流
    BufferedInputStream inBuff = new BufferedInputStream(input);
    // 新建文件輸出流並對它進行緩衝
    FileOutputStream output = new FileOutputStream(targetFile);
    BufferedOutputStream outBuff = new BufferedOutputStream(output);
    // 緩衝數組
    byte[] b = new byte[1024 * 5];
    int len;
    while ((len = inBuff.read(b)) != -1) {
        outBuff.write(b, 0, len);
    }
    // 刷新此緩衝的輸出流
    outBuff.flush();
    //關閉流;輸入流和輸出流都需要close。注意順序,先開的最後close
    inBuff.close();
    outBuff.close();
    output.close();
    input.close();
}
notes:     //注意下面這句僅僅是打開流,不是讀寫流
BufferedInputStream inBuff = new BufferedInputStream(input);

3.1.2 字符流

public class FileReaderDemo {
    public static void main(String[] args) throws IOException {
        // 定義源文件
        File file = new File("E:\\test.txt");
        Reader reader = new FileReader(file);   
        // 獲取文件名
        String fileName = file.getName();
        // 定義寫文件路徑
        String aimPath =  fileName+".out";
        Writer writer = new FileWriter(aimPath);    
        // 定義字符數組,每次一個數組一個數組讀
        char[] chars = new char[1024];
        while (reader.read(chars) != -1) {
            writer.write(chars);
        }
                //每次一個char一個char讀寫
                // char[] c=new char[1024];
                // int temp=0 ,len=0;
                // while((temp=input.read())!=-1){
                //          c[len]=(char) temp;
               //           len++;
               //    }
        writer.flush();
        writer.close();
        reader.close();
    }

注意:有IO buffer一定要用flush。所有IO流和所有文件句柄都要關閉.
flush 和close的區分在於,flush之後buffer清空,繼續使用;close之後buffer不再能用。
常見用法:BufferedReader in= new BufferedReader(new FileReader("Text.java"));

3.2 轉換流

OutputStreamWriter 字符流轉字節流

File f = new File ("D:\\output.txt");
// OutputStreamWriter 是字符流通向字節流的橋樑,創建了一個字符流通向字節流的對象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(f),"UTF-8");
osw.write("我是字符流轉換成字節流輸出的");

InputStreamReader 字節流轉字符流。

File f = new File("D:\\output.txt");
//字節流轉成字符流
InputStreamReader inr = new InputStreamReader(new FileInputStream(f),"UTF-8");    
 char[] buf = new char[1024];     
int len = inr.read(buf);

notes:轉換流和字符流類似,按照字符讀寫。它是在字節流基礎上二次讀寫流.
InputStreamReader(FileInputStream(new file));InputStreamReader只是轉存儲方式,byte變成char(具體是StreamDecoder 實現)
cout<<charbuffer ,應用層纔是按照編碼方式(unicode表,而不是ascii)讀取和識別字符
or 字節.

3.3 標準IO

字節流

try {
//System.in is InputStream;System.in提供的 read方法每次只能讀取一個字節的數據
//在控制檯(console)每次只能輸入一個字符,然後System.in按照字節讀取
    int read = System.in.read();
    System.out.println(read);//輸出ascii
} catch(IOException e){
    e.printStackTrace() ;
}

字符流

    char cbuf[] = new char[1024];
        //接收鍵盤錄入,需要你在控制檯輸入數據後按回車鍵
        BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
    int a = read.read(cbuf);
    System.out.println(cbuf);

常見用法:BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
notes:scanner class也可以用於read 標準IO和file,但是通常使用BufferReader方式。後者比前者具有效率高等優點。

3.4 讀寫socket

 Socket client = new Socket(host, port);
//socket和system.in一樣當成字節流. 先轉成字符流讀寫
Writer writer = new OutputStreamWriter(client.getOutputStream());
 writer.write("Hello From Client");

3.5 序列化和反序列化ObjectOutputStream

objectwriter=new ObjectOutputStream(new FileOutputStream("C:/student.txt"));  
objectwriter.writeObject(new Student("gg", 22));
class Student implements Serializable{  
   private String name;  
   private int age;  
   public Student(String name, int age) {  
      super();  
      this.name = name;  
      this.age = age;  
   }
}

ObjectOutputStream的性能相對差,而且不能跨平臺.現在常用protobuffer.
各種序列化性能比較
https://colobu.com/2014/08/26/java-serializer-comparison/

3.6 ByteArrayOutputStream

ByteArrayOutputStream和BufferedOutputStream 非常相似.
那麼 ByteArrayOutputStream 和BuffereOutputStream 區別是什麼呢?
StackOver 對兩者區別的解釋,
Generally BufferedOutputStream wrapper is mostly used to avoid frequent disk or network writes. It can be much more expensive to separately write a lot of small pieces than make several rather large operations. The ByteArrayOutputStream operates in memory, so I think the wrapping is pointless.
BufferedInputStream 那些文件,socket操作,ByteArrayOutputStream也能做。但是沒有BufferedInputStream好用,所以通常不用。ByteArrayOutputStream 常用於內存操作.
常用用法:讀寫內存(string)

eg1:
public static void main(String[] args) throws IOException {
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    DataOutputStream dout = new DataOutputStream(bout);
    String name = "xxy";
    int age = 84;
    dout.writeUTF(name);
    dout.writeInt(age);
    byte[] buff = bout.toByteArray();
    //開闢緩衝區
    ByteArrayInputStream bin = new ByteArrayInputStream(buff);
    DataInputStream dis = new DataInputStream(bin);
    String newName = dis.readUTF();
    int newAge = dis.readInt();
    System.out.println(newName + ":" + newAge);
}
eg2: ByteArrayOutputStream  //網絡通信
public static void main(String[] args) throws IOException {
    private OutputStream toAgent = null;
    toAgent = localSocket.getOutputStream();
    ByteArrayOutputStream msgByteStream = new ByteArrayOutputStream();
   //先寫buffer,然後寫socket
    msgByteStream.write("hello world");
    msgByteStream.writeTo(toAgent);
    //如果換成BufferedOutputStream 
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(toAgent);
    bufferedOutputStream.write("hello world");
}

3.7 "格式化"輸入/輸出DataOutputStream/DataInputStream

可以輸出所有類型

dos = new DataOutputStream(new FileOutputStream("d://dataTest.txt"));
        dos.writeInt(18888);
        dos.writeByte(123);
        dos.writeFloat(1.344f);
        dos.writeBoolean(true);
        dos.writeChar(49);
    dos.writeBytes("世界"); //按2字節寫入,都是寫入的低位
    dos.writeChars("世界"); // 按照Unicode寫入
// 按照UTF-8寫入(UTF8變長,開頭2字節是由writeUTF函數寫入的長度信息,方便readUTF函數讀取)
    dos.writeUTF("世界");

常見用法:
DataInputStream in=new DataInputStream(new ByteArrayInputStream(str.getBytes()));
DataInputStream in=new DataInputStream(new BufferedInputStream(new FileInputStream("Data.txt")));
DataOutputStream dos= new DataOutputStream(new BufferedOutputStream(new FileOutputStream("Data.txt")));

3.8 Java,c++ 讀寫中文字符 (兩種方法writer和DataOutputStream)(漢字需要指定UTF-8讀寫)

3.8.1 c++直接讀寫,java轉成Reader/Writer+UTF-8 讀寫

c++

        // 以寫模式打開文件
    string love_cpp = "我愛你中國123";
    ofstream outfile;
    outfile.open("afile.dat");
    outfile << love_cpp.c_str() << endl;
    outfile.close();

Java

FileWrite和OutputStreamWriter
兩者效果相同。但是FileWriter默認編碼不是UTF-8,所以直接讀寫會產生亂碼。
FileWriter fw=new FileWriter(file); fw.write(..) ;   錯誤
FileWriter fw=new FileWriter(file); fw.write(..,"UTF-8") ;  正確       
        //讀取漢字需要添加編碼類型
        OutputStreamWriter osw = new OutputStreamWriter(new 
        FileOutputStream(f),"UTF-8");
        osw.write("我是字符流轉換成字節流輸出的123");
        osw.close();

tricky:c++,java都可以一次性讀寫文件。

3.8.2 爲什麼字節流不能輸出漢字?

因爲字節流,1一個字節一個字節處理,編碼的方式是ascii,範圍是-127-127。比如:“我” 輸出是-50,-46.
所以輸出漢字只能用字符流 (字符流是雙字節處理,加上UTF-8是三字節處理).

3.9 其他常見用法

PrintWriter pw=new PrintWriter(new BufferedWriter("text.out"));
PrintWriter pw=new PrintWriter(System.out,true);
PrintStream ps= new PrintStream(new BufferedOutputStream(new FileOutputStream("text.out")));

3.10 Examples link

BufferedOutputStream
BufferedInputStream

4 Java 和 c++ 流區別?

Java的Stream對象分成字節流和字符流,而且需要自己緩衝.
C++的steam對象統一,任何流都可以按字節、字符串、整形的方式讀或者寫,c++封裝了緩衝。

5 BufferedInputStream 底層實現和應用場景

5.1 BufferedInputStream 源碼實現

就是在inputstream 之上wrap了一個8k的buffer。如果buffer空了(或者不夠),再次調用fill函數將buffer讀滿。stackover的解釋:For example, your file is 32768 bytes long.
To get all the bytes in memory with a FileInputStream, you will require 32768 native calls to the OS.
With a BufferedInputStream, you will only require 4, regardless of the number of read() calls you will do (still 32768).

5.2 BufferedInputStream和 inputstream

Inputstream不是每次只能讀寫一個字節.底層實現兩者都是本地方法readBytes(byte b[], int off, int len),這個方法底層是可以一次性拷貝多個字節的
BufferedInputStream和inputstream都可以一次讀寫多個字節。
BufferedInputStream 實現,詳見BufferedInputStream
如果用Inputstream讀取buffer array的方式,等於自己寫了一個buffer 管理類,即BufferedInputStream。
BufferedInputStream還封裝了readline,mark,reset 3個功能.
stackover: BufferedOutputStream wrapper is mostly used to avoid frequent disk or network writes

5.3 buffer >8k,buffer <8k

inputstream 讀寫bytes >8k,inputstream和BufferedInputStream 效率差不多。(BufferedInputStream封裝寫的好一點,效率略高)
inputstream 讀寫bytes <8k,inputstream和BufferedInputStream 效率差很多。
詳見 FileInputStream 與 BufferedInputStream 效率對比
inputstream 讀寫小於8k(比如80bytes),造成多次讀寫硬盤。BufferedInputStream先放入buffer,累積夠了8k再讀寫一次硬盤,效率高。

6 裝飾者模式與io關係

6.1 什麼是裝飾者模式

Decorator pattern
java I/O庫中設計模式的應用
具體分3步:1):將被裝飾者通過裝飾者的構造函數,傳遞給裝飾者。2): 使用傳入的被裝飾者的屬性 3):在2)的基礎上加上裝飾者的東東,兩者合一形成新的結果。
br = BufferedInputStream(fileinputstream f) 將fileinputstream 傳入構造函數,
br.read base fileinputstream.read 接口基礎上,wrap read,即br.read 調用fileinputstream readBytes(byte b[], int off, int len).
notes:裝飾者模式就是添加東東。

6.2 裝飾者模式與io關係

new BufferedInputStream(new InputStreamRead(new inputstream)); 一層一層對stream 添加修飾(即提高流的效率).
Bufferinputstream實際作用就是調用了fileinputstream的帶長度read,而不是缺省的一個一個read。
這篇文章的實例很說明問題: 學習、探究Java設計模式——裝飾者模式
//下面,我們來自己實現自己的JavaIO的裝飾者。要實現的功能是:把一段話裏面的每個單詞的首字母大寫。我們先新建一個類:UpperFirstWordInputStream.java

public class UpperFirstWordInputStream extends FilterInputStream {
    private int cBefore = 32;
    protected UpperFirstWordInputStream(InputStream in) {
        //由於FilterInputStream已經保存了裝飾對象的引用,這裏直接調用super即可
        super(in);
    }
    public int read() throws IOException{
        //根據前一個字符是否是空格來判斷是否要大寫
        int c = super.read();
        if(cBefore == 32)
        {
            cBefore = c;
            return (c == -1 ? c: Character.toUpperCase((char) c));
        }else{
            cBefore = c;
            return c;
        }
    }
}
//接着編寫一個測試類:InputTest.java
public class InputTest {
    public static void main(String[] args) throws IOException {
        int c;
        StringBuffer sb = new StringBuffer();
        try {
            //這裏用了兩個裝飾者,分別是BufferedInputStream和我們的UpperFirstWordInputStream
            InputStream in = new UpperFirstWordInputStream(new BufferedInputStream(new FileInputStream("test.txt")));
            while((c = in.read()) >= 0)
            {
                sb.append((char) c);
            }
            System.out.println(sb);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

7 適配器模式與IO

7.1 什麼是適配器模式

[適配器模式]
(http://www.runoob.com/design-pattern/adapter-pattern.html)
一個示例讓你明白適配器模式
上面那個link的適配器非常好。
hotel只提供兩口插座powerWithTwoRound,如何適配powerWithThreeRound呢?
hotel是不能改變的,powerWithThreeRound是不能改變的。中間增加了一個轉換器。
SocketAdapter implements DBSocketInterface 接口繼承powerWithTwoRound。爲了能傳入hotel的接口(即構造函數)
實際內部實現不用powerWithTwoRound的實現,改成了powerWithThreeRound的實現。披了一層powerWithThreeRound的class的外衣,把裏面的“同名”實現函數的具體內容換了。
這樣就實現了調用powerWithThreeRound函數的目的。
表面是調用一個接口,實際執行的是另一個接口的內容。(類似,插座前面是三相的,尾部是二相的。只給外面看3相的接口)
notes:適配器就是“舊瓶裝新酒”,進去的時候和出去的時候不一樣.

7.2 適配器模式與java IO

上面的適配器模式,是僅僅用了接口,直接調用了另一個接口的實現。這是最簡單的adaptor模式。
adaptor模式也可以做內部轉換。輸入是字節流,經過內部adaptor轉換,輸出轉換成了字符流。
InputStreamReader和OutputStreamWriter源碼分析
StreamDecoder

7.3 適配器模式優點:接口不變

靈活性和擴展性都非常好,通過使用配置文件,可以很方便地更換適配器,也可以在不修改原有代碼的基礎上增加新的適配器類,完全符合“開閉原則”。

8 裝飾者模式和適配器模式的區別

兩者都是base傳入的原型,內部處理。
裝飾者沒有改變原型性質,僅僅是優化。比如對字符流僅僅批處理。
adaptor模式:是改變性質。接口不變。字節流變成了字符流。

備註1:c,c++ 讀寫文件

c 讀寫文件(eg: copy 文件)
pf1 = fopen("1.mp3", "rb")
 while(fread(buf,1,256,pf1), !feof(pf1))
 {
  fwrite(buf,1,256,pf2);
 }
c++ 讀寫文件
  fstream fin("1.mp3",ios::in|ios::binary);
  fout<<fin.rdbuf();

備註2:c++ 缺省採用系統自動緩衝。自定義緩衝使用setbuf。

備註3:Java Read/Write實現

底層read/write 不是內部循環寫,直到寫完爲止。是每次read/write不能超過IO buffer(通常4k). 超過IO buffer,write會寫錯,write return -1.

   while((n = read(infd, buf, 1024)) > 0 ){
        write(outfd, buf, n);
    }

所以BufferedInputStream 8k的buffer,一定要while調用幾次調用write寫

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