一.前言
1.IO流概述
IO流分为输入输出流。
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在量设备间的传输称为流。
流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观地进行数据操作
2.IO流的分类
根据处理数据类型的不同分为:字节流和字符流
根据数据流向不同分为:输入流和输出流
3.理解输入输出流两个概念
在java api中,可以从中读入一个字节序列的对象称为输入流,而可以向其中写入一个字节序列的对象称为输入流。
可以这样理解,输入流表示程序从一个源读取数据,输出流表示程序向一个目标写数据。
这两者可以认为是相对于程序来说的,程序向文件写入东西,是输出流,所以用OutputStream;程序从文件读取东西,是输入流,用InputStream。
4.IO流图示
如图所示,java的io体系其实不难,so,我们可以分几步来学习
①首先是各种字节输入流的超类InputStream && 字节输出流的超类OutputStream.
②接着是各种字符输入流的超类Reader && 字符输出流的超类Writer.
③然后是转换流InputStreamReader && OutputStreamWriter,这两个类用于字符字节流之间的转换。
二.字节流
1.字节输入输出流
①输出流
Outputstream类
public abstract class OutputStream implements Closeable, Flushable
此抽象类是表示输出字节流的所有类的超类。输出流接受输出字节并将这些字节发送到Inputstream类的某个接收器中
向文件中输出,一般用他的子类FileoutputStream。
②输入流
Inputstream类
public abstract class InputStream implements Closeable
此抽象类表示输入字节流的所有类的超类。
从文件系统中的某个文件获得输入字节,一般用他的子类FileInputStream。
2.Outputstream类使用
/**
* 简单字节输出流 || 写
*/
private static void out() {
try {
//1.输出流对象,第二个位置为true,表示追加,不写则表示覆盖
OutputStream os = new FileOutputStream(new File("C:\\Users\\sisheng\\Desktop\\out.txt"), true);
//2.要输出的内容
String message = "hello world\r\n"; //windows下,换行符号
//3.输出到文件,写入文件
os.write(message.getBytes());
//4.一定要关闭流
os.close();
System.out.println("write success");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
3.Inputstream类使用
/**
* 简单字节输入流 || 读
*/
private static void in() {
try {
//1.输入流对象
InputStream in = new FileInputStream(new File("C:\\Users\\sisheng\\Desktop\\out.txt"));
//2.声明一个字节数组,相当于去运书的小车
byte[] bytes = new byte[1024]; //1兆,这里注意是基本类型byte,不是包装类型Byte
//3.声明一个长度变量作为输入流的长度,当长度为-1,说明读到底了
int length = -1;
//4.构造字符串存储读到的字节
StringBuilder sb = new StringBuilder();
java.lang.String s = new java.lang.String(bytes);
//5.不断读取字节
while (-1 != (length = in.read(bytes))) {
sb.append(new java.lang.String(bytes,0,length));
}
System.out.println(sb);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
注意:
①变量length的作用:我们看到,in.read(bytes)返回的是输入流读取到的字节数目,当读取到尽头的时候返回-1,说明读取完毕。
②length的第二个作用:sb.append(new java.lang.String(bytes,0,length));,为什么将字节数组转成字符串时要指定长度呢。假设代码改成 byte[] bytes = new byte[10];
那么读入“学习是很快乐的”,那么第一次循环读到“学习是很快”10个字节,第二次循环自然读剩下的“乐的”这4个字节,但是由于是字节数组存储,第二次读完后bytes的前4个字节确实变了,但后6个字节保持不变,那么转换过来的字符串就是“乐的是很快”。
③字节流是没有缓存机制的,执行完write(),数据就会直接写入内存,但字符流有
4.字节输入流源码分析
①OutputStream.write()
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
②上面的write方法
public void write(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
可以看到,写入是按照一个一个字节来的
5.字节输出流源码分析
①Inputstream.read()
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
②上面的read()
public int read(byte b[], int off, int len) throws IOException {
...
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
③继续看上面的read()
public abstract int read() throws IOException;
这个就看不到源码了,老师说是底层用c或c++实现的。
所以,读取操作也是一个一个读的。
三.字符流
1.字符流使用:Writer and Reader
/**
* 输入流 || 读
*/
private static void in() {
try {
Reader r = new FileReader(new File("C:\\Users\\sisheng\\Desktop\\out.txt"));
char[] chars = new char[1];
int len = -1;
StringBuilder sb = new StringBuilder();
while (-1 != (len=(r.read(chars)))) { //这里读也是一样的,自带缓存
sb.append(new String(chars,0,len));
}
System.out.println(sb);
r.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 输出流 || 写
*/
private static void out() {
try {
Writer w = new FileWriter(new File("C:\\Users\\sisheng\\Desktop\\out.txt"), true);
String message = ",追梦赤子心";
w.write(message); //这句话执行后就会将字符串缓存在内存,并没有真正写入文件里。Writer源码里有缓存大小变量为1024
w.close(); //close之后会将缓存的数据全部写入文件
} catch (IOException e) {
e.printStackTrace();
}
}
2.缓存机制
Writer的源码中有个变量指定了默认的缓存大小1024:private static final int WRITE_BUFFER_SIZE = 1024;也就是1兆,在执行完write方法后并没与直接写入文件。执行close()方法后会是缓存的数据全部写入文件。字节流则没有这种机制。
四.字符流与字节流的区别
在所有的流操作中,字节永远是最基础的。任何基于字节的操作都是正确的。无论你是文本文件还是二进制的文件。如果确认流里面只有可打印的字符,包括英文的和各个国家的文字,也包括中文,那么而已考虑用字符流。由于编码不同,多字节的字符可能占用多个字节。比如GBK的汉字就占用两个字节。而UTF8的汉字就占用三个字节。所以,字符流是根据指定的编码,将1个或者多个字节转化为java中的unicode字符,然后再进行操作。字符操作一般使用Writer,Reader等,字节操作一般都是Inputstream,OutputStream以及各种包装类,不如BufferedInputStream和BufferedOutputstream等。
总结:如果你确认要处理的流是可打印的字符,那么用字符流看上去会简单一些。如果不确认,用字节流总是没错的。