什么是流: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是对流的缓冲,增加效率.
字节流导图
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);
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"));
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<
字节流
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方式。后者比前者具有效率高等优点。
Socket client = new Socket(host, port);
//socket和system.in一样当成字节流. 先转成字符流读写
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello From Client");
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/
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");
}
可以输出所有类型
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")));
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都可以一次性读写文件。
因为字节流,1一个字节一个字节处理,编码的方式是ascii,范围是-127-127。比如:“我” 输出是-50,-46.
所以输出汉字只能用字符流 (字符流是双字节处理,加上UTF-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")));
BufferedOutputStream
BufferedInputStream
Java的Stream对象分成字节流和字符流,而且需要自己缓冲.
C++的steam对象统一,任何流都可以按字节、字符串、整形的方式读或者写,c++封装了缓冲。
就是在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).
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
inputstream 读写bytes >8k,inputstream和BufferedInputStream 效率差不多。(BufferedInputStream封装写的好一点,效率略高)
inputstream 读写bytes <8k,inputstream和BufferedInputStream 效率差很多。
详见 FileInputStream 与 BufferedInputStream 效率对比
inputstream 读写小于8k(比如80bytes),造成多次读写硬盘。BufferedInputStream先放入buffer,累积够了8k再读写一次硬盘,效率高。
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:装饰者模式就是添加东东。
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();
}
}
[适配器模式]
(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:适配器就是“旧瓶装新酒”,进去的时候和出去的时候不一样.
上面的适配器模式,是仅仅用了接口,直接调用了另一个接口的实现。这是最简单的adaptor模式。
adaptor模式也可以做内部转换。输入是字节流,经过内部adaptor转换,输出转换成了字符流。
InputStreamReader和OutputStreamWriter源码分析
StreamDecoder
灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
两者都是base传入的原型,内部处理。
装饰者没有改变原型性质,仅仅是优化。比如对字符流仅仅批处理。
adaptor模式:是改变性质。接口不变。字节流变成了字符流。
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<
底层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写