Java

Java/C++ IO 实例详解

2018-11-05  本文已影响77人  mfdalf

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)是转化

2 Java IO 导图

字节流导图

3 Java 各种场景使用实例 (读String,Socket,读文件,标准IO)

3.1 文件

3.1.1 字节流

in = new FileInputStream("C:\\mycode\\hello.txt");
out = new FileOutputStream("C:\\mycode\\hello-copy.txt", true);
            byte[] temp = new byte[1024];
            int length = 0;
            while ((length = in.read(temp)) != -1) {
                out.write(temp, 0, length);
            }

3.1.2 字符流

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);
注意:有IO buffer一定要用flush。所有IO流和所有文件句柄都要关闭.
flush 和close的区分在于,flush之后buffer清空,继续使用;close之后buffer不再能用。
常见用法:BufferedReader in= new BufferedReader(new FileReader("Text.java"));

3.2 转换流

OutputStreamWriter 字符流转字节流,InputStreamReader 字节流转字符流。
字符流转字节流

File f = new File ("D:\\output.txt");
// OutputStreamWriter 是字符流通向字节流的桥梁,创建了一个字符流通向字节流的对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(f),"UTF-8");
osw.write("我是字符流转换成字节流输出的");

字节流转换成字符流

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读取和识别字符)

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 和BufferedInputStream 区别
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)

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);
}

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

而这对流就完成了一个对基本数据类型(boolean,byte,unsignedByte,short,unsignedShort,char,int,long,float,double)的写入和读取,读取UTF格式等
比如写UTF-8文件,只能用 DataOutputStream

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 其他常见用法

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.9 Java,c++ 读写中英文字符 (汉字需要指定UTF-8读写)

3.9.1 c++直接读写,java转成Reader/writer 读写

c++ 
// 以写模式打开文件
    string love_cpp = "我爱你中国123";
    ofstream outfile;
    outfile.open("afile.dat");
    outfile << love_cpp.c_str() << endl;
    outfile.close();
Java        
// OutputStreamWriter 是字符流通向字节流的桥梁,创建了一个字符流通向字节流的对象
    //读取汉字需要添加编码类型
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(f),"UTF-8");
        osw.write("我是字符流转换成字节流输出的123");
        osw.close();

3.9.2 FileReader 和OutputStreamWriter(new FileOutputStream)

两者效果相同。但是FileReader默认编码不是UTF-8,所以直接读写会产生乱码。
FileWriter fw=new FileWriter(file); fw.write(..) ; 错误
FileWriter fw=new FileWriter(file); fw.write(..,"UTF-8") ; 正确
tricky:c++,java都可以一次性读写文件。

3.9.3 为什么字节流不能输出汉字?

因为字节流,1一个字节一个字节处理,编码的方式是ascii,范围是-127-127。比如:“我” 输出是-50,-46.
所以输出汉字只能用字符流 (字符流是双字节处理,加上UTF-8是三字节处理).

3.10 Examples link

BufferedOutputStream
BufferedInputStream

4 Java 和 c++ 流区别?

Java的Stream对象(除了PrintStream)功能较单一,只能按字节读写,需要Reader或者Writer的辅助。
C++的任何流都可以按字节、字符串、整形的方式读或者写。

5 字节流和字符流读写文本比较

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).

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相的接口)

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: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写.

上一篇 下一篇

猜你喜欢

热点阅读