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写

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