面试官:Java中缓冲流真的性能很好吗?我看未必

简介: 【6月更文挑战第9天】面试官:Java中缓冲流真的性能很好吗?我看未必

一、写在开头

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

二、衍生数据流分类

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

2.1 文件流

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

2.2 数组流

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

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

public class TestService{
   
   
    public static void main(String[] args)  {
   
   
        try {
   
   
            ByteArrayInputStream bi = new ByteArrayInputStream("JavaBuild".getBytes());
            int content;
            while ((content = bi.read()) != -1) {
   
   
                System.out.print((char) content);
            }
            // 关闭输入流,释放资源
            bi.close();
        } catch (Exception e) {
   
   
            e.printStackTrace();
        }
    }
}

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

2.3 管道流

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

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

public class TestService{
   
   
    public static void main(String[] args) throws IOException {
   
   
        // 创建一个 PipedOutputStream 对象和一个 PipedInputStream 对象
        final PipedOutputStream pipedOutputStream = new PipedOutputStream();
        final PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream);

        // 创建一个线程,向 PipedOutputStream 中写入数据
        Thread thread1 = new Thread(new Runnable() {
   
   
            @Override
            public void run() {
   
   
                try {
   
   
                    // 将字符串 "沉默王二" 转换为字节数组,并写入到 PipedOutputStream 中
                    pipedOutputStream.write("My name is JavaBuild".getBytes());
                    // 关闭 PipedOutputStream,释放资源
                    pipedOutputStream.close();
                } catch (IOException e) {
   
   
                    e.printStackTrace();
                }
            }
        });
        // 创建一个线程,从 PipedInputStream 中读取数据并输出到控制台
        Thread thread2 = new Thread(new Runnable() {
   
   
            @Override
            public void run() {
   
   
                try {
   
   
                    // 定义一个字节数组用于存储读取到的数据
                    byte[] flush = new byte[1024];
                    // 定义一个变量用于存储每次读取到的字节数
                    int len = 0;
                    // 循环读取字节数组中的数据,并输出到控制台
                    while (-1 != (len = pipedInputStream.read(flush))) {
   
   
                        // 将读取到的字节转换为对应的字符串,并输出到控制台
                        System.out.println(new String(flush, 0, len));
                    }
                    // 关闭 PipedInputStream,释放资源
                    pipedInputStream.close();
                } catch (IOException e) {
   
   
                    e.printStackTrace();
                }
            }
        });
        // 启动线程1和线程2
        thread1.start();
        thread2.start();
    }
}

2.4 数据流

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

image.png

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

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

2.5 缓冲流

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

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

public
class BufferedInputStream extends FilterInputStream {
   
   
    // 内部缓冲区数组
    protected volatile byte buf[];
    // 缓冲区的默认大小
    private static int DEFAULT_BUFFER_SIZE = 8192;
    // 使用默认的缓冲区大小
    public BufferedInputStream(InputStream in) {
   
   
        this(in, DEFAULT_BUFFER_SIZE);
    }
    // 自定义缓冲区大小
    public BufferedInputStream(InputStream in, int size) {
   
   
        super(in);
        if (size <= 0) {
   
   
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
}

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

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

public class TestService{
   
   
    public static void main(String[] args) throws IOException {
   
   
        TestService testService = new TestService();
        testService.copyPdfWithPublic();
        testService.copyPdfWithBuffer();
    }
    /*通过普通文件流进行pdf文件的读取和拷贝*/
    public void copyPdfWithPublic(){
   
   
        // 记录开始时间
        long start = System.currentTimeMillis();
        try (FileInputStream fis = new FileInputStream("E:\\Java性能权威指南.pdf");
             FileOutputStream fos = new FileOutputStream("E:\\Java性能权威指南Public.pdf")) {
   
   
            int content;
            while ((content = fis.read()) != -1) {
   
   
                fos.write(content);
            }
            //使用数组充当缓存时,两者性能差距不大
            /*int len;
            byte[] bytes = new byte[4 * 1024];
            while ((len = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, len);
            }*/
            fis.close();
            fos.close();
        } catch (IOException e) {
   
   
            e.printStackTrace();
        }
        // 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("使用普通文件流复制PDF文件总耗时:" + (end - start) + " 毫秒");
    }
    /*通过缓冲字节流进行pdf文件的读取和拷贝*/
    public void copyPdfWithBuffer(){
   
   
        // 记录开始时间
        long start = System.currentTimeMillis();
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\Java性能权威指南.pdf"));
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\Java性能权威指南Buffer.pdf"))) {
   
   
            int content;
            while ((content = bis.read()) != -1) {
   
   
                bos.write(content);
            }
            /*int len;
            byte[] bytes = new byte[4 * 1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }*/
            bis.close();
            bos.close();
        } catch (IOException e) {
   
   
            e.printStackTrace();
        }
        // 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("使用缓冲字节流复制PDF文件总耗时:" + (end - start) + " 毫秒");
    }
}

输出:

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

image.png

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

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

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

2.6 打印流

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

public class PrintStream extends FilterOutputStream
    implements Appendable, Closeable {
   
   
}
public class PrintWriter extends Writer {
   
   
}
目录
相关文章
|
5天前
|
缓存 监控 Java
Java虚拟机(JVM)性能调优实战指南
在追求软件开发卓越的征途中,Java虚拟机(JVM)性能调优是一个不可或缺的环节。本文将通过具体的数据和案例,深入探讨JVM性能调优的理论基础与实践技巧,旨在为广大Java开发者提供一套系统化的性能优化方案。文章首先剖析了JVM内存管理机制的工作原理,然后通过对比分析不同垃圾收集器的适用场景及性能表现,为读者揭示了选择合适垃圾回收策略的数据支持。接下来,结合线程管理和JIT编译优化等高级话题,文章详细阐述了如何利用现代JVM提供的丰富工具进行问题诊断和性能监控。最后,通过实际案例分析,展示了性能调优过程中可能遇到的挑战及应对策略,确保读者能够将理论运用于实践,有效提升Java应用的性能。 【
37 10
|
9天前
|
算法 Java 编译器
透视Java语言的究极优化:探索性能的深度
在Java程序员的日常工作中,优化代码性能是一项至关重要的任务。然而,除了传统的性能调优方法外,本文将探讨一些更为深奥的技术,如JIT编译器的内部工作机制、GC算法的进阶应用以及多线程并发模型的优化策略。通过深入了解这些技术背后的原理和实现,我们可以更好地理解如何在Java平台上实现最高效的代码运行。 【7月更文挑战第11天】
20 4
|
10天前
|
缓存 监控 算法
Java面试题:讨论JVM性能调优的常见方法和技巧。
Java面试题:讨论JVM性能调优的常见方法和技巧。
15 1
|
9天前
|
Java 编译器 API
Java中的Lambda表达式:简化代码,提升性能
在Java 8中,Lambda表达式的引入为开发者提供了一种更加简洁、灵活的编程方式。本文将深入探讨Lambda表达式的概念、语法、使用场景及其在Java中的应用示例,帮助读者更好地理解和掌握这一强大工具,从而优化代码结构,提高开发效率。
|
10天前
|
Java
Java面试题:什么是G1垃圾收集器,它如何改善性能?
Java面试题:什么是G1垃圾收集器,它如何改善性能?
9 0
|
10天前
|
消息中间件 Java 中间件
Java面试题:解释分布式事务的概念,讨论常见的分布式事务解决方案。
Java面试题:解释分布式事务的概念,讨论常见的分布式事务解决方案。
17 0
|
10天前
|
缓存 搜索推荐 Java
Java面试题:简述CAP理论及其在分布式系统设计中的应用。请提供一个具体的例子,说明在系统设计中如何取舍一致性和可用性
Java面试题:简述CAP理论及其在分布式系统设计中的应用。请提供一个具体的例子,说明在系统设计中如何取舍一致性和可用性
13 0
|
10天前
|
设计模式 安全 NoSQL
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
13 0
|
10天前
|
设计模式 存储 缓存
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
15 0
|
10天前
|
存储 设计模式 监控
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
19 0