目录
(一)字节缓冲流拷贝文件——BufferedInputStream、BufferedOutputStream
字节缓冲输入流 BufferedInputStream
字节缓冲输出流 BufferedOutputStream
字符缓冲输入流 BufferedReader
字符缓冲输出流 BufferedWriter
一、字节缓冲输入流与字节缓冲输出流
原理:底层自带了长度为8192的缓冲区提高性能。
缓冲流是不能直接读取文件中的数据的,也不能直接把数据写到文件当中。在创建缓冲流对象的时候需要关联基本流,真正读写数据的,还是基本流。只不过有了缓冲流的加持,读写效率更高而已。

(一)字节缓冲流拷贝文件——BufferedInputStream、BufferedOutputStream
1.一次读写一个字节
注意:只关闭缓冲流即可,因为关闭之前会默认把基本流关闭。
public class BufferedStreamDemo1 {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\test\\aaa\\movie.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("chapter18\\copy.mp4"));
// 读取文件
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bis.close();
bos.close();
}
}
2.一次读写一个字节数组
public class BufferedStreamDemo2 {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\test\\ccc\\图像1.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("chapter18\\copy图像1.jpg"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
bis.close();
}
}
(二)字节缓冲流提高效率的原理
字节缓冲流(BufferedInputStream 和 BufferedOutputStream)在Java中通过使用内部缓冲区来提高 IO 操作的效率。其核心原理是减少对底层系统资源(如磁盘、网络等)的直接访问次数,从而提升整体的 IO 性能。下面详细解释其提高效率的原理:
1.字节流与字节缓冲流的区别
字节流:InputStream和OutputStream以及它们的子类,如FileInputStream和FileOutputStream,用于字节级别的输入和输出操作。这些流在读取或写入每一个字节时都会直接与底层资源交互,例如磁盘或网络。
字节缓冲流:BufferedInputStream和BufferedOutputStream是InputStream和OutputStream的包装类,它们通过内部的缓冲区一次性读取或写入大量数据,从而减少底层资源的访问频率。
2.缓冲区的作用
缓冲区是一个临时存储数据的内存区域。字节缓冲流在读取或写入数据时,会先将数据放入缓冲区,而不是每次操作都直接访问磁盘或网络。
减少系统调用:直接读取或写入文件系统或网络资源是相对较慢的操作,每次 IO 操作都会涉及到昂贵的系统调用。字节缓冲流通过一次性读取或写入较大块的数据,将这些操作集中处理,从而减少了系统调用的次数。
3.字节缓冲流的工作原理

(1)BufferedInputStream的工作原理
当调用 read()方法时,BufferedInputStream 会先从它的缓冲区中读取数据。
如果缓冲区为空(或用尽),BufferedInputStream会通过底层输入流(如 FileInputStream)从磁盘一次性读取一大块数据到缓冲区中。
这样,当后续读取数据时,直接从内存中的缓冲区中获取数据,而不需要每次都访问磁盘,显著提高了读取效率。
(2)BufferedOutputStream的工作原理
当调用write()方法时,BufferedOutputStream不会立即将数据写入底层输出流(如 FileOutputStream)。相反,它会先将数据写入内部的缓冲区。
当缓冲区满了,或者调用flush()方法时,缓冲区中的数据才会一次性写入底层输出流。这样避免了每次写入一个字节就进行一次系统调用的情况,从而提高了写入效率。
(3)缓冲流默认缓冲区大小与底层的数据类型
缓冲流自带了长度为8192的缓冲区,可以通过构造函数手动指定缓冲区的大小:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.txt"), 16384); // 16 KB 缓冲区
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.txt"), 16384); // 16 KB 缓冲区
字节缓冲流的底层是byte类型,长度是8KB:

字符缓冲流的底层是char类型,长度是16KB:
每个字符占用2个字节,所以是16KB的字节空间

二、字符缓冲流
原理:底层自带了长度为8192的缓冲区提高性能。
字符流的基本流当中,底层已经有了缓冲区了,因此,字符缓冲流对于字符流提升效率不明显,对于字符缓冲流而言,关键点是两个特有的方法(readLine()和newLine())。
1.字符缓冲流的构造方法

2.字符缓冲流特有方法
| 方法名称 | 说明 |
|---|---|
| public String readLine() | 字符缓冲输入流特有方法 读取一行数据,如果读到文件末尾, 没有数据可读了,会返回null readLine方法在读取的时候,一次读一整行,遇到回车换行结束 但是它不会把回车换行读到内存当中 所以使用System.out.println进行手动换行,否则文件中所有数据会打印在一行 |
| public void newLine() | 字符缓冲输出流特有方法 跨平台的换行 |
3.字符缓冲输入流读取文件
public class BufferedStreamDemo3 {
public static void main(String[] args) throws IOException {
// 字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("chapter18\\d.txt"));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
}
运行结果:

4.字符缓冲输出流写出文件
public class BufferedStreamDemo4 {
public static void main(String[] args) throws IOException {
// 开启续写
BufferedWriter bw = new BufferedWriter(new FileWriter("chapter18\\g.txt", true));
bw.write("我爱北京天安门,");
bw.newLine();
bw.write("天安门上太阳升。");
bw.newLine();
bw.close();
}
}
运行结果:

三、字节缓冲流和字符缓冲流综合练习(重要)
1.四种方式拷贝文件,并统计各自用时
public class TestDemo4 {
public static void main(String[] args) throws IOException {
test1();
test2();
test3();
test4();
}
public static void test1() throws IOException {
FileInputStream fis = new FileInputStream("D:\\test\\aaa\\movie.mp4");
FileOutputStream fos = new FileOutputStream("D:\\test\\aaa\\bbb\\ccc\\movie1.mp4");
long start = System.currentTimeMillis();
int len;
while ((len = fis.read()) != -1) {
fos.write(len);
}
fis.close();
long end = System.currentTimeMillis();
long time = end - start;
System.out.println("test1持续时间:" + time + "毫秒");
}
public static void test2() throws IOException {
FileInputStream fis = new FileInputStream("D:\\test\\aaa\\movie.mp4");
FileOutputStream fos = new FileOutputStream("D:\\test\\aaa\\bbb\\ccc\\movie2.mp4");
long start = System.currentTimeMillis();
int len;
byte[] bytes = new byte[8192];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fis.close();
long end = System.currentTimeMillis();
long time = end - start;
System.out.println("test2持续时间:" + time + "毫秒");
}
public static void test3() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\test\\aaa\\movie.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\test\\aaa\\bbb\\ccc\\movie3.mp4"));
long start = System.currentTimeMillis();
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bis.close();
long end = System.currentTimeMillis();
long time = end - start;
System.out.println("test3持续时间:" + time + "毫秒");
}
public static void test4() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\test\\aaa\\movie.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\test\\aaa\\bbb\\ccc\\movie4.mp4"));
long start = System.currentTimeMillis();
int len;
byte[] bytes = new byte[8192];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bis.close();
long end = System.currentTimeMillis();
long time = end - start;
System.out.println("test4持续时间:" + time + "毫秒");
}
}
运行结果:

因此,文件的拷贝可以使用一次读写一个字节数组相对较快。
2.把《出师表》的文章顺序进行恢复到一个新文件中
csb.txt文件
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨补阙漏,有所广益。
4.将军向宠,性行淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐托付不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言。深追先帝遗诏,臣不胜受恩感激。
9.今当远离,临表涕零,不知所言。
(1)方式一:使用ArrayList
public class TestDemo5 {
public static void main(String[] args) throws IOException {
// 读取文件,根据.进行分割,存到集合中,排序,写出
BufferedReader br = new BufferedReader(new FileReader("chapter18\\csb.txt"));
ArrayList<String> list = new ArrayList<>();
String len;
// 读取文件内容,直到文件末尾
while ((len = br.readLine()) != null) {
list.add(len);
}
br.close();
System.out.println(list);
// 集合中的元素排序
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int split1 = Integer.parseInt(o1.split("\\.")[0]);
int split2 = Integer.parseInt(o2.split("\\.")[0]);
return split1 - split2;
}
});
System.out.println(list);
BufferedWriter bw = new BufferedWriter(new FileWriter("chapter18\\newcsb.txt"));
for (String s : list) {
bw.write(s);
bw.newLine();
}
bw.close();
}
}
(2)方式二:使用TreeMap
public class TestDemo6 {
public static void main(String[] args) throws IOException {
// 使用TreeMap自动排序
BufferedReader br = new BufferedReader(new FileReader("chapter18\\csb.txt"));
TreeMap<Integer, String> treeMap = new TreeMap<>();
String len;
while ((len = br.readLine()) != null) {
String[] split = len.split("\\.");
treeMap.put(Integer.parseInt(split[0]), split[1]);
}
br.close();
System.out.println(treeMap);
BufferedWriter bw = new BufferedWriter(new FileWriter("chapter18\\newcsb1.txt"));
Set<Map.Entry<Integer, String>> entries = treeMap.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
Integer key = entry.getKey();
String value = entry.getValue();
String str = key + "." + value;
bw.write(str);
bw.newLine();
}
bw.close();
}
}
3.控制软件运行次数
实现一个验证程序运行次数的小程序,要求如下:
1.当程序运行超过3次时给出提示:本软件只能免费使用3次,欢迎您注册会员后继续使用~
2.程序运行演示如下:
第一次运行控制台输出: 欢迎使用本软件,第1次使用免费~
第二次运行控制台输出: 欢迎使用本软件,第2次使用免费~
第三次运行控制台输出: 欢迎使用本软件,第3次使用免费~
第四次及之后运行控制台输出:本软件只能免费使用3次,欢迎您注册会员后继续使用~
public class TestDemo7 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("chapter18\\count.txt"));
// 读取
String line = br.readLine();
br.close();
int count = Integer.parseInt(line);
count++;
if (count <= 3) {
System.out.println("欢迎使用本软件,第" + count + "次使用免费~");
} else {
System.out.println("本软件只能免费使用3次,欢迎您注册会员后继续使用~");
}
// 写出
BufferedWriter bw = new BufferedWriter(new FileWriter("chapter18\\count.txt"));
bw.write(count + "");
bw.close();
}
}
4.统计项目文件中,.java文件的总行数
public class TestDemo {
public static void main(String[] args) throws IOException {
// 统计文件中代码行数,过滤.java文件
File file = new File("G:\\develop\\workspace");
// 方式一:
getJavaCount(file);
System.out.println(javaLine); // 29275
// 方式二:
count(file);
System.out.println(count);
}
static int javaLine = 0;
private static void getJavaCount(File file) throws IOException {
BufferedReader bis;
File[] files = file.listFiles();
for (File f : files) {
if (f.isFile()) {
if (f.getName().endsWith(".java")) {
// 读取文件
bis = new BufferedReader(new FileReader(f));
while (bis.readLine() != null) {
javaLine++;
}
bis.close();
}
} else {
getJavaCount(f);
}
}
}
static int count = 0;
private static void count(File file) throws IOException {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
count(f);
}
} else if (file.getName().endsWith(".java")) {
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
while (bufferedReader.readLine() != null) {
count++;
}
}
}
}
四、IO流使用小贴士
IO流随用随创建,什么时候不用什么时候关闭。
如果是对同一个文件进行操作,同时开启输入输出流,文件会被清空,只会读取缓冲区中的数据。
323

被折叠的 条评论
为什么被折叠?



