经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
面试官:Java中缓冲流真的性能很好吗?我看未必
来源:cnblogs  作者:JavaBuild  时间:2024/6/17 15:13:01  对本文有异议

一、写在开头

上一篇文章中,我们介绍了Java IO流中的4个基类:InputStream、OutputStream、Reader、Writer,那么这一篇中,我们将以四个基类所衍生出来,应对不同场景的数据流进行学习。
image

二、衍生数据流分类

我们上面说了java.io包中有40多个类,都从InputStream、OutputStream、Reader、Writer这4个类中衍生而来,我们以操作对象的维度进行如下的区分:

2.1 文件流

文件流也就是直接操作文件的流,可以细分为字节流(FileInputStream 和 FileOuputStream)和字符流(FileReader 和 FileWriter),我们在上面的已经说了很多了,这里就再赘述啦。

2.2 数组流

所谓数组流就是将内存中有限的数据进行读写操作的流,适应于数据量小,无需利用文件存储,提升程序效率。

我们以ByteArrayInputStream(字节数组输入流)为例:

  1. public class TestService{
  2. public static void main(String[] args) {
  3. try {
  4. ByteArrayInputStream bi = new ByteArrayInputStream("JavaBuild".getBytes());
  5. int content;
  6. while ((content = bi.read()) != -1) {
  7. System.out.print((char) content);
  8. }
  9. // 关闭输入流,释放资源
  10. bi.close();
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }

字节数组输出流(ByteArrayOutputStream)亦是如此,它们不需要创建临时文件,直接在内存中就可以完成对字节数组的压缩,加密,读写以及序列化。

2.3 管道流

管道(Pipe)作为一种在计算机内通讯的媒介,无论是在操作系统(Unix/Linux)层面还是JVM层面都至关重要,我们今天提到的通道流就是在JVM层面,同一个进程中不同线程之间数据交互的载体。

我们以PipedOutputStream和PipedInputStream为例,通过PipedOutputStream将一串字符写入到内存中,再通过PipedInputStream读取输出到控制台,整个过程并没有临时文件的事情,数据仅在两个线程之间流转。

  1. public class TestService{
  2. public static void main(String[] args) throws IOException {
  3. // 创建一个 PipedOutputStream 对象和一个 PipedInputStream 对象
  4. final PipedOutputStream pipedOutputStream = new PipedOutputStream();
  5. final PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream);
  6. // 创建一个线程,向 PipedOutputStream 中写入数据
  7. Thread thread1 = new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. try {
  11. // 将字符串 "沉默王二" 转换为字节数组,并写入到 PipedOutputStream 中
  12. pipedOutputStream.write("My name is JavaBuild".getBytes());
  13. // 关闭 PipedOutputStream,释放资源
  14. pipedOutputStream.close();
  15. } catch (IOException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. });
  20. // 创建一个线程,从 PipedInputStream 中读取数据并输出到控制台
  21. Thread thread2 = new Thread(new Runnable() {
  22. @Override
  23. public void run() {
  24. try {
  25. // 定义一个字节数组用于存储读取到的数据
  26. byte[] flush = new byte[1024];
  27. // 定义一个变量用于存储每次读取到的字节数
  28. int len = 0;
  29. // 循环读取字节数组中的数据,并输出到控制台
  30. while (-1 != (len = pipedInputStream.read(flush))) {
  31. // 将读取到的字节转换为对应的字符串,并输出到控制台
  32. System.out.println(new String(flush, 0, len));
  33. }
  34. // 关闭 PipedInputStream,释放资源
  35. pipedInputStream.close();
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. }
  39. }
  40. });
  41. // 启动线程1和线程2
  42. thread1.start();
  43. thread2.start();
  44. }
  45. }

2.4 数据流

我们知道在Java中分为基本数据类型和引用类型,我们在做数据的读取与写入时,自然也会涉及到这种情况,比如我们将txt文件中的数字型数据以int类型读取到程序中,这时Java为我们提供了DataInputStream/DataOutputStream类。它们的常用方法为:

image

具体使用也相对比较简单:

  1. DataInputStream dis = new DataInputStream(new FileInputStream("input.txt"));
  2. // 创建一个 DataOutputStream 对象,用于将数据写入到文件中
  3. DataOutputStream das = new DataOutputStream(new FileOutputStream("output.txt"));
  4. // 读取四个字节,将其转换为 int 类型
  5. int i = dis.readInt();
  6. // 将一个 int 类型的数据写入到文件中
  7. das.writeInt(1000);

2.5 缓冲流

对于数据的处理,CPU速度快于内存,内存又远快于硬盘,在大数据量情况下,频繁的通过IO向磁盘读写数据会带来严重的性能问题,为此Java中提供了一个缓冲流的概念,简单来说就是在内存中设置一个缓冲区,只有缓冲区中存储的数据到达一定量后才会触发一次IO,这样大大提升了程序的读写性能,常用的缓冲流有:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter。
image

通过BufferedInputStream的底层源码我们可以看到,其内部维护了一个buf[]数据,默认大小为8192字节,我么也可以通过构造函数进行缓存大小设置。

  1. public
  2. class BufferedInputStream extends FilterInputStream {
  3. // 内部缓冲区数组
  4. protected volatile byte buf[];
  5. // 缓冲区的默认大小
  6. private static int DEFAULT_BUFFER_SIZE = 8192;
  7. // 使用默认的缓冲区大小
  8. public BufferedInputStream(InputStream in) {
  9. this(in, DEFAULT_BUFFER_SIZE);
  10. }
  11. // 自定义缓冲区大小
  12. public BufferedInputStream(InputStream in, int size) {
  13. super(in);
  14. if (size <= 0) {
  15. throw new IllegalArgumentException("Buffer size <= 0");
  16. }
  17. buf = new byte[size];
  18. }
  19. }

至于说缓冲流到底能不能实现性能的提升,我们实践出真知,对于程序员来说所有的理论都不及上手写一写来得有效!这其实也涉及到一个经常被问的面试问题:java中的缓冲流真的性能很好吗?

刚好,我们手头有一本《Java性能权威指南》的PDF版,大小为66MB,我们通过普通的文件流和缓冲流进行文件的读取和复制,看一下耗时对比。

  1. public class TestService{
  2. public static void main(String[] args) throws IOException {
  3. TestService testService = new TestService();
  4. testService.copyPdfWithPublic();
  5. testService.copyPdfWithBuffer();
  6. }
  7. /*通过普通文件流进行pdf文件的读取和拷贝*/
  8. public void copyPdfWithPublic(){
  9. // 记录开始时间
  10. long start = System.currentTimeMillis();
  11. try (FileInputStream fis = new FileInputStream("E:\\Java性能权威指南.pdf");
  12. FileOutputStream fos = new FileOutputStream("E:\\Java性能权威指南Public.pdf")) {
  13. int content;
  14. while ((content = fis.read()) != -1) {
  15. fos.write(content);
  16. }
  17. //使用数组充当缓存时,两者性能差距不大
  18. /*int len;
  19. byte[] bytes = new byte[4 * 1024];
  20. while ((len = fis.read(bytes)) != -1) {
  21. fos.write(bytes, 0, len);
  22. }*/
  23. fis.close();
  24. fos.close();
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. // 记录结束时间
  29. long end = System.currentTimeMillis();
  30. System.out.println("使用普通文件流复制PDF文件总耗时:" + (end - start) + " 毫秒");
  31. }
  32. /*通过缓冲字节流进行pdf文件的读取和拷贝*/
  33. public void copyPdfWithBuffer(){
  34. // 记录开始时间
  35. long start = System.currentTimeMillis();
  36. try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\Java性能权威指南.pdf"));
  37. BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\Java性能权威指南Buffer.pdf"))) {
  38. int content;
  39. while ((content = bis.read()) != -1) {
  40. bos.write(content);
  41. }
  42. /*int len;
  43. byte[] bytes = new byte[4 * 1024];
  44. while ((len = bis.read(bytes)) != -1) {
  45. bos.write(bytes, 0, len);
  46. }*/
  47. bis.close();
  48. bos.close();
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. }
  52. // 记录结束时间
  53. long end = System.currentTimeMillis();
  54. System.out.println("使用缓冲字节流复制PDF文件总耗时:" + (end - start) + " 毫秒");
  55. }
  56. }

输出:

  1. 使用普通文件流复制PDF文件总耗时:221611 毫秒
  2. 使用缓冲字节流复制PDF文件总耗时:228 毫秒

image

然后,我们将注释掉的代码放开,也就是我们采用一个缓存数组,先将数组存储起来后,两者之间的性能差距就没那么明显了。

  1. 使用普通文件流复制PDF文件总耗时:106 毫秒
  2. 使用缓冲字节流复制PDF文件总耗时:80 毫秒

在这种情况下,我们可以看到,甚至于普通的文件流的耗时是小于缓冲流的,所以对于这种情况来说,缓冲流未必一定性能最好。

2.6 打印流

对于System.out.println("Hello World");这句代码我想大家并不陌生吧,我们刚学习Java的第一堂课,老师们都会让我们输出一个Hello World,System.out 实际是用于获取一个 PrintStream 对象,print方法实际调用的是 PrintStream 对象的 write 方法。

  1. public class PrintStream extends FilterOutputStream
  2. implements Appendable, Closeable {
  3. }
  4. public class PrintWriter extends Writer {
  5. }

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!
image

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!
image

原文链接:https://www.cnblogs.com/JavaBuild/p/18251692

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号