Java/C++ IO 實(shí)例詳解

1 Java 字節(jié)流(byte),字符流(char,string)區(qū)別?

什么是流:IO操作就是流。比如,標(biāo)準(zhǔn)輸入輸出,讀寫文件,內(nèi)存賦值。
字節(jié),字符區(qū)別:byte 1個(gè)字節(jié),java char is 兩個(gè)字節(jié). c++ char is 1個(gè)字節(jié)
應(yīng)用場景:字符流用于是文本,字節(jié)流用于所有場景。
常用字節(jié)流:ByteArrayInputStream,ObjectInputStream,FileInputStream,
FilterInputStream(BufferedInputStream,DataInputStream)。output同樣。
常用字符流:BufferedRead,FileReader. Writer同樣.
轉(zhuǎn)換流:InputStreamReader,OutputStreamWriter.
關(guān)鍵字:Reader/Writer 是字符流,Input/output是字節(jié)流 。既有input(output)又有reader(writer)是轉(zhuǎn)化;Buffer是對流的緩沖,增加效率.

2 Java IO 導(dǎo)圖

字節(jié)流導(dǎo)圖

3 Java 各種場景使用實(shí)例 (讀String,Socket,讀文件,標(biāo)準(zhǔn)IO)

3.1 文件

3.1.1 字節(jié)流(字節(jié)緩沖流)

public static void copyFile(File sourceFile, File targetFile)
        throws IOException {
    // 新建文件輸入流并對它進(jìn)行緩沖
    FileInputStream input = new FileInputStream(sourceFile);
    //注意這僅僅是打開流,不是讀寫流
    BufferedInputStream inBuff = new BufferedInputStream(input);
    // 新建文件輸出流并對它進(jìn)行緩沖
    FileOutputStream output = new FileOutputStream(targetFile);
    BufferedOutputStream outBuff = new BufferedOutputStream(output);
    // 緩沖數(shù)組
    byte[] b = new byte[1024 * 5];
    int len;
    while ((len = inBuff.read(b)) != -1) {
        outBuff.write(b, 0, len);
    }
    // 刷新此緩沖的輸出流
    outBuff.flush();
    //關(guān)閉流;輸入流和輸出流都需要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);    
        // 定義字符數(shù)組,每次一個(gè)數(shù)組一個(gè)數(shù)組讀
        char[] chars = new char[1024];
        while (reader.read(chars) != -1) {
            writer.write(chars);
        }
                //每次一個(gè)char一個(gè)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流和所有文件句柄都要關(guān)閉.
flush 和close的區(qū)分在于,flush之后buffer清空,繼續(xù)使用;close之后buffer不再能用。
常見用法:BufferedReader in= new BufferedReader(new FileReader("Text.java"));

3.2 轉(zhuǎn)換流

OutputStreamWriter 字符流轉(zhuǎn)字節(jié)流

File f = new File ("D:\\output.txt");
// OutputStreamWriter 是字符流通向字節(jié)流的橋梁,創(chuàng)建了一個(gè)字符流通向字節(jié)流的對象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(f),"UTF-8");
osw.write("我是字符流轉(zhuǎn)換成字節(jié)流輸出的");

InputStreamReader 字節(jié)流轉(zhuǎn)字符流。

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

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

3.3 標(biāo)準(zhǔn)IO

字節(jié)流

try {
//System.in is InputStream;System.in提供的 read方法每次只能讀取一個(gè)字節(jié)的數(shù)據(jù)
//在控制臺(tái)(console)每次只能輸入一個(gè)字符,然后System.in按照字節(jié)讀取
    int read = System.in.read();
    System.out.println(read);//輸出ascii
} catch(IOException e){
    e.printStackTrace() ;
}

字符流

    char cbuf[] = new char[1024];
        //接收鍵盤錄入,需要你在控制臺(tái)輸入數(shù)據(jù)后按回車鍵
        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 標(biāo)準(zhǔn)IO和file,但是通常使用BufferReader方式。后者比前者具有效率高等優(yōu)點(diǎn)。

3.4 讀寫socket

 Socket client = new Socket(host, port);
//socket和system.in一樣當(dāng)成字節(jié)流. 先轉(zhuǎn)成字符流讀寫
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的性能相對差,而且不能跨平臺(tái).現(xiàn)在常用protobuffer.
各種序列化性能比較
https://colobu.com/2014/08/26/java-serializer-comparison/

3.6 ByteArrayOutputStream

ByteArrayOutputStream和BufferedOutputStream 非常相似.
那么 ByteArrayOutputStream 和BuffereOutputStream 區(qū)別是什么呢?
StackOver 對兩者區(qū)別的解釋,
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 常用于內(nèi)存操作.
常用用法:讀寫內(nèi)存(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();
    //開辟緩沖區(qū)
    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  //網(wǎng)絡(luò)通信
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字節(jié)寫入,都是寫入的低位
    dos.writeChars("世界"); // 按照Unicode寫入
// 按照UTF-8寫入(UTF8變長,開頭2字節(jié)是由writeUTF函數(shù)寫入的長度信息,方便readUTF函數(shù)讀取)
    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轉(zhuǎn)成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默認(rèn)編碼不是UTF-8,所以直接讀寫會(huì)產(chǎn)生亂碼。
FileWriter fw=new FileWriter(file); fw.write(..) ;   錯(cuò)誤
FileWriter fw=new FileWriter(file); fw.write(..,"UTF-8") ;  正確       
        //讀取漢字需要添加編碼類型
        OutputStreamWriter osw = new OutputStreamWriter(new 
        FileOutputStream(f),"UTF-8");
        osw.write("我是字符流轉(zhuǎn)換成字節(jié)流輸出的123");
        osw.close();

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

3.8.2 為什么字節(jié)流不能輸出漢字?

輸入比較簡單,讀取流不需要考慮格式。printf屬于應(yīng)用層,所以需要考慮漢字,英文,數(shù)字的編碼格式,按照編碼方式輸出才能顯示對應(yīng)的漢字or英文.如果輸出編碼方式不對,顯示亂碼. 字節(jié)流,1一個(gè)字節(jié)一個(gè)字節(jié)處理,編碼的方式是ascii,范圍是-127-127。比如:“我” 輸出是-50,-46.所以輸出漢字只能用字符流 (字符流是雙字節(jié)處理,加上UTF-8是三字節(jié)處理).

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++ 流區(qū)別?

Java的Stream對象分成字節(jié)流和字符流,沒有自適應(yīng)性(英文or中文),字節(jié)流換成字符流才能讀出漢字,同時(shí)英文or數(shù)字也按照兩個(gè)byte讀取. 而且需要自己緩沖.
C++的steam對象統(tǒng)一,任何流都可以按字節(jié)、字符串、或者整形的方式讀或者寫,c++封裝了緩沖。

5 BufferedInputStream 底層實(shí)現(xiàn)和應(yīng)用場景

5.1 BufferedInputStream 源碼實(shí)現(xiàn)

就是在inputstream 之上wrap了一個(gè)8k的buffer。如果buffer空了(或者不夠),再次調(diào)用fill函數(shù)將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不是每次只能讀寫一個(gè)字節(jié).底層實(shí)現(xiàn)兩者都是本地方法readBytes(byte b[], int off, int len),這個(gè)方法底層是可以一次性拷貝多個(gè)字節(jié)的
BufferedInputStream和inputstream都可以一次讀寫多個(gè)字節(jié)。
BufferedInputStream 實(shí)現(xiàn),詳見BufferedInputStream
如果用Inputstream讀取buffer array的方式,等于自己寫了一個(gè)buffer 管理類,即BufferedInputStream。
BufferedInputStream還封裝了readline,mark,reset 3個(gè)功能.
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封裝寫的好一點(diǎn),效率略高)
inputstream 讀寫bytes <8k,inputstream和BufferedInputStream 效率差很多。
詳見 FileInputStream 與 BufferedInputStream 效率對比
inputstream 讀寫小于8k(比如80bytes),造成多次讀寫硬盤。BufferedInputStream先放入buffer,累積夠了8k再讀寫一次硬盤,效率高。

6 裝飾者模式與io關(guān)系

6.1 什么是裝飾者模式

Decorator pattern
java I/O庫中設(shè)計(jì)模式的應(yīng)用
具體分3步:1):將被裝飾者通過裝飾者的構(gòu)造函數(shù),傳遞給裝飾者。2): 使用傳入的被裝飾者的屬性 3):在2)的基礎(chǔ)上加上裝飾者的東東,兩者合一形成新的結(jié)果。
br = BufferedInputStream(fileinputstream f) 將fileinputstream 傳入構(gòu)造函數(shù),
br.read base fileinputstream.read 接口基礎(chǔ)上,wrap read,即br.read 調(diào)用fileinputstream readBytes(byte b[], int off, int len).
notes:裝飾者模式就是添加?xùn)|東。

6.2 裝飾者模式與io關(guān)系

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

public class UpperFirstWordInputStream extends FilterInputStream {
    private int cBefore = 32;
    protected UpperFirstWordInputStream(InputStream in) {
        //由于FilterInputStream已經(jīng)保存了裝飾對象的引用,這里直接調(diào)用super即可
        super(in);
    }
    public int read() throws IOException{
        //根據(jù)前一個(gè)字符是否是空格來判斷是否要大寫
        int c = super.read();
        if(cBefore == 32)
        {
            cBefore = c;
            return (c == -1 ? c: Character.toUpperCase((char) c));
        }else{
            cBefore = c;
            return c;
        }
    }
}
//接著編寫一個(gè)測試類:InputTest.java
public class InputTest {
    public static void main(String[] args) throws IOException {
        int c;
        StringBuffer sb = new StringBuffer();
        try {
            //這里用了兩個(gè)裝飾者,分別是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)
一個(gè)示例讓你明白適配器模式
上面那個(gè)link的適配器非常好。
hotel只提供兩口插座powerWithTwoRound,如何適配powerWithThreeRound呢?
hotel是不能改變的,powerWithThreeRound是不能改變的。中間增加了一個(gè)轉(zhuǎn)換器。
SocketAdapter implements DBSocketInterface 接口繼承powerWithTwoRound。為了能傳入hotel的接口(即構(gòu)造函數(shù))
實(shí)際內(nèi)部實(shí)現(xiàn)不用powerWithTwoRound的實(shí)現(xiàn),改成了powerWithThreeRound的實(shí)現(xiàn)。披了一層powerWithThreeRound的class的外衣,把里面的“同名”實(shí)現(xiàn)函數(shù)的具體內(nèi)容換了。
這樣就實(shí)現(xiàn)了調(diào)用powerWithThreeRound函數(shù)的目的。
表面是調(diào)用一個(gè)接口,實(shí)際執(zhí)行的是另一個(gè)接口的內(nèi)容。(類似,插座前面是三相的,尾部是二相的。只給外面看3相的接口)
notes:適配器就是“舊瓶裝新酒”,進(jìn)去的時(shí)候和出去的時(shí)候不一樣.

7.2 適配器模式與java IO

上面的適配器模式,是僅僅用了接口,直接調(diào)用了另一個(gè)接口的實(shí)現(xiàn)。這是最簡單的adaptor模式。
adaptor模式也可以做內(nèi)部轉(zhuǎn)換。輸入是字節(jié)流,經(jīng)過內(nèi)部adaptor轉(zhuǎn)換,輸出轉(zhuǎn)換成了字符流。
InputStreamReader和OutputStreamWriter源碼分析
StreamDecoder

7.3 適配器模式優(yōu)點(diǎn):接口不變

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

8 裝飾者模式和適配器模式的區(qū)別

兩者都是base傳入的原型,內(nèi)部處理。
裝飾者沒有改變原型性質(zhì),僅僅是優(yōu)化。比如對字符流僅僅批處理。
adaptor模式:是改變性質(zhì)。接口不變。字節(jié)流變成了字符流。

備注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();
https://blog.csdn.net/bookish_2010_prj/article/details/5454771

備注2:c++ 缺省采用系統(tǒng)自動(dòng)緩沖。自定義緩沖使用setbuf。

備注3:Java Read/Write實(shí)現(xiàn)

底層read/write 不是內(nèi)部循環(huán)寫,直到寫完為止。是每次read/write不能超過IO buffer(通常4k). 超過IO buffer,write會(huì)寫錯(cuò),write return -1.

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

所以BufferedInputStream 8k的buffer,一定要while調(diào)用幾次調(diào)用write寫.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概述 java.io 包幾乎包含了所有操作輸入、輸出需要的類。所有這些流類代表了輸入源和輸出目標(biāo)。java.io ...
    Steven1997閱讀 9,444評論 1 25
  • tags:io categories:總結(jié) date: 2017-03-28 22:49:50 不僅僅在JAVA領(lǐng)...
    行徑行閱讀 2,306評論 0 3
  • 五、IO流 1、IO流概述 (1)用來處理設(shè)備(硬盤,控制臺(tái),內(nèi)存)間的數(shù)據(jù)。(2)java中對數(shù)據(jù)的操作都是通過...
    佘大將軍閱讀 597評論 0 0
  • 1 IO(二)No19 【 緩沖流:內(nèi)置了緩沖區(qū),對現(xiàn)有的流對象進(jìn)行了封裝,實(shí)現(xiàn)了高效的讀寫操作并增強(qiáng)了功能 ...
    征程_Journey閱讀 817評論 0 1
  • 如果我死了, 請把我的遺體捐給醫(yī)院, 讓他們拿走心肝脾肺腎, 死了,也有價(jià)值。 如果我死了, 請把我的遺體火化成骨...
    峻峸閱讀 612評論 0 1

友情鏈接更多精彩內(nèi)容