字节缓冲流BufferedInputStream&BufferedOutputStream
Java缓冲流本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流(包装流)。
当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地,在IO操作时记得加上缓冲流来提升性能。
BufferedInputStream和BufferedOutputStream这两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率。
下面是字节缓冲流的一个实例:
package com.company;
import java.io.*;
/**使用字节缓冲流拷贝视频对比字节使用文件字节流拷贝
* @author 发达的范
* @version 1.0
* @date 2021/04/04 15:39
*/
public class BufferedTest01 {
public static void main(String[] args) {
String srcPath = "E:/(A)发达的范/Videos/波士顿动力/波士顿动力-Do You Love Me-.mp4";
String destPath = "E:/(A)发达的范/Videos/波士顿动力/copy-波士顿动力-Do You Love Me-.mp4";
long t1 = System.currentTimeMillis();
copy(srcPath,destPath);
long t2 = System.currentTimeMillis();
System.out.println(t2 - t1);
}
public static void copy(String srcPath, String destPath) {
File src = new File(srcPath);
File dest = new File(destPath);
try (InputStream is = new BufferedInputStream(new FileInputStream(src)); OutputStream os = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] flush = new byte[1024];
int len=-1;
while ((len = is.read(flush)) != -1) {
os.write(flush, 0, len);
}
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
对比不加字节缓冲流的拷贝用时:998ms,拷贝速度提升了998/190=5.25倍。
BufferedInputStream和BufferedOutputStream都是直接套在文件字节流上即可
这里使用的是try…catch…with…,不用写释放资源的程序
在关闭流时,应该先关闭最外层的包装流,即“后开的先关闭”。
缓存区的大小默认是8192字节(8KB),也可以使用其它的构造方法自己指定大小。
到这里,我在想一个问题:为什么加了一个缓冲区读写速度就变快了?
我是这样理解的:加了一个缓冲区之后,每次先把读/写的数据存到缓冲区中,缓冲区满后,再把缓冲区的数据一次性读入/写入,假设从文件中读取一段字节然后写入共N次,加了缓冲流之后,写入次数会变少,也就是节省了写入数据的次数。但是能做到节省的前提是每次读入到flush字节数组的大小要小于BufferedInputStream的缓冲区的大小。另外,如果是访问网络上的数据,不使用缓冲流,在访问过程中,如果由于网络状态不好而导致访问数据不成功,或者过慢,那么每次read操作就会变慢,导致整体变慢,如果使用缓冲流,就可以在一定程度上改善这种情况。
另外,关于流的关闭,这个程序使用的是try…catch…with…形式,所以不用手动关闭流,但是正常的关闭流的操作是:
- 先打开的后关闭,最底层的先关闭
- 如果使用了缓冲流,那么直接关闭缓冲流即可,因为缓冲流的源码中会自动先关闭位于一线的节点流
字符缓冲流BufferedReader&BufferedWriter
字节缓冲流和字符缓冲流尽量不要发生多态,因为缓冲流提供的新增方法是父类没有的,所以可能使用不到这些简便方法。
BufferedReader新增方法readLine,读一行字符,程序自动寻找换行符
BufferedWriter新增方法newLine,写换行符
使用件字符输入/输出缓冲流,进行文件拷贝的程序
package com.company;
import java.io.*;
/**
* 使用文件字符输入/输出缓冲流,进行文件拷贝
* 逐行读取,逐行写入
*
* @author 发达的范
* @version 1.0
* @date 2021/04/05 10:14
*/
public class BufferedTest04 {
public static void main(String[] args) {
Copy("src/com/company/asd.txt","src/com/company/copy-asd.txt");
}
public static void Copy(String srcPath, String destPath) {
//1.确定源
File src = new File(srcPath);
File dest = new File(destPath);
//2.选择流
try (BufferedReader br = new BufferedReader(new FileReader(src));
BufferedWriter bw = new BufferedWriter(new FileWriter(dest))) {
String line = null;
while ((line = br.readLine()) != null) {
bw.append(line);
bw.newLine();
}
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结:使用字节缓冲流可以使用多态,但是使用字符缓冲流尽量不要使用多态,因为字符缓冲流有独有的方法newLine(),父类没有这个方法。
转换流InputStreamReader&OutputStreamWriter
System.in是字节流对象,代表键盘的输入流,如果我们想按行接收用户的输入时,就必须用到缓冲字符流BufferedReader特有的方法readLine(),但是经过观察会发现在创建BufferedReader的构造方法的参数必须是一个Reader对象,这时候我们的转换流InputStreamReader就派上用场了。
而System.out也是字节流对象,代表输出到显示器,按行读取用户的输入后,并且要将读取的一行字符串直接显示到控制台,就需要用到字符流的write(String str)方法,所以我们要使用OutputStreamWriter将字节流转化为字符流。
简而言之,转换流处理的都是字节流,都是把字节流转换成字符流。
InputStreamReader是把输入的字节流包装成字符流,然后就可以使用输入字符缓冲流来处理数据;OutputStreamWriter是把输出的字节流包装成字符流,然后就可以使用输出字符缓冲流。
下面是InputStreamReader/OutputStreamWriter的程序实例:
package com.company;
import java.io.*;
/**
* 转换流InputStreamReader,OutputStreamWriter
* 都是把字节流转换成字符流进行处理
*
* @author 发达的范
* @version 1.0
* @date 2021/04/05 15:29
*/
public class BufferedTest05 {
public static void main(String[] args) {
//转换System.in,System.out,都是字节流
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out))) {
String msg = "";
while (!msg.equals("exit")) {
msg = reader.readLine();
writer.write(msg);
writer.newLine();
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
其中:
//转换System.in,System.out,都是字节流
InputStreamReader isr = new InputStreamReader(System.in);//转换成字符流
OutputStreamWriter osw = new OutputStreamWriter(System.out);//转换成字符流
BufferedReader reader = new BufferedReader(isr);//使用字符缓冲流包装上面转换成的字符流
BufferedWriter writer = new BufferedWriter(osw);//使用字符缓冲流包装上面转换成的字符流
需要注意是:这里使用了字符缓冲流,而字符缓冲流是有缓存空间的,如果没有达到缓存空间就不会输出,也就是程序中没有这句writer.flush();无法正常输出,因为每次手动输入的数据不可能达到8KB,强制刷新是把滞留在内存中的数据push出。
下面是使用网络地址流测试转换流InputStreamReader的程序
package com.company;
import java.io.*;
import java.net.URL;
/**
* 转换流,使用网络数据测试
*
* @author 发达的范
* @version 1.0
* @date 2021/04/05 16:03
*/
public class Convert01 {
public static void main(String[] args) {
test01();
// test02();
}
public static void test01() {
try (BufferedReader reader =
new BufferedReader(
new InputStreamReader(
new URL("https://www.baidu.com").openStream(),"utf-8"))) {
String msg = "";
while ((msg = reader.readLine()) != null) {
System.out.println(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void test02() {
try (InputStream is = new URL("https://www.baidu.com").openStream()) {
int temp = -1;
while ((temp = is.read()) != -1) {//每次读取一个字符
System.out.print((char) temp);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果(在不影响分析的情况下只截取了部分):
-
程序中使用的是打开一个网络地址流,new URL(“https://www.baidu.com”).openStream();
-
可以看到,程序中实例化转换流InputStreamReader时指定了解码字符集(charset)为UTF-8,当然也可以指定其他字符集,但是可能出现乱码;
-
之所以可以使用转换流InputStreamReader,是因为这个网页的源代码流都是文本,所以可以正确的包装转换成字符;
上述程序如果运行test02,则输出结果如下:
可以看到,这里从网页获取到的源代码的中文出现乱码,这个地方不是字符集的问题,而是我使用的是字节输入流,每次读取一个字节,,这个网页的编码方式是UTF-8,我这个工程的编码方式也是UTF-8,每次读取一个字节,英文字母可以正确输出,而中文占3个字节每次读取一个字节输出肯定会出现乱码。
如果把URL换成网易官网(https://www.163.com),程序使用默认解码方式(UTF-8),输出结果如下:
可见出现了大量的中文乱码,因为网易官网的编码方式是GBK,下面在程序中把解码方式改成GBK,运行结果如下:
下面测试输出转换流OutputStreamWriter
package com.company;
import java.io.*;
import java.net.URL;
/**
* 转换流,使用网络数据测试
*
* @author 发达的范
* @version 1.0
* @date 2021/04/05 16:03
*/
public class Convert01 {
public static void main(String[] args) {
test01();
}
public static void test01() {
try (BufferedReader reader =
new BufferedReader(
new InputStreamReader(
new URL("https://www.baidu.com").openStream(), "utf-8"));
BufferedWriter writer =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("baidu.html"), "utf-8"))) {
String msg = "";
while ((msg = reader.readLine()) != null) {//每次读取一行
writer.write(msg);//每次写一行
writer.newLine();
}
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
从程序中可以看到
BufferedWriter writer =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("baidu.html"), "utf-8"))
这里new FileOutputStream(“baidu.html”)是使用文件字节输出流输出到文件(baidu.html),然后转换流使用OutputStreamWriter转换成输出字符流,并指定编码格式,然后使用字符缓冲输出流进行包装处理。