
本文纲要字节流拷贝的回顾与痛点字节缓冲流概述缓冲流源码分析缓冲区大小与自动关闭缓冲流一次读写一个字节的实现与原理4.1 代码实现4.2 底层原理与内存模型缓冲流结合字节数组的高效拷贝5.1 代码实现5.2 与单字节方式的区别四种拷贝方式对比总结项目代码结构完整代码示例小结字节流拷贝的回顾与痛点在学习 Java IO 的初期我们通常使用FileInputStream和FileOutputStream进行文件拷贝。常见的两种基础方式为一次读写一个字节每次从源文件读取一个字节再写入目标文件。这种方式虽然简单但每次读写都需要与硬盘交互性能极低。一次读写一个字节数组手动创建一个byte[]数组例如new byte[1024]每次读取1024字节再写入目标文件。这种方式减少了与硬盘的交互次数性能大幅提升。Java 的设计者显然意识到了手动管理数组的麻烦因此在标准库中提供了字节缓冲流内置了缓冲区让开发者无需手动创建数组即可获得高效的文件读写能力。字节缓冲流概述Java 中的字节缓冲流分为两种BufferedInputStream字节缓冲输入流用于高效读取数据。BufferedOutputStream字节缓冲输出流用于高效写出数据。核心特点缓冲流本身不直接操作文件必须依赖底层的字节流如FileInputStream/FileOutputStream才能真正读写数据。缓冲流的本质是在内存中提供了一个默认长度为8192字节的数组用于暂存数据从而减少与硬盘的交互次数大幅提升效率。当关闭缓冲流时其关联的底层字节流也会被自动关闭。缓冲流源码分析缓冲区大小与自动关闭通过查看 JDK 源码我们可以验证缓冲区的大小和关闭行为。1 ) 缓冲区大小在BufferedInputStream的构造方法中会调用另一个构造方法内部使用DEFAULTBUFFERSIZE常量// BufferedInputStream 源码片段privatestaticintDEFAULTBUFFERSIZE8192;publicBufferedInputStream(InputStreamin){this(in,DEFAULTBUFFERSIZE);}在底层构造中会创建一个长度为 size 的字节数组byte[]bufnewbyte[size];同样BufferedOutputStream的构造方法也遵循相同逻辑底层创建长度为8192的字节数组。2 ) 自动关闭底层流在close()方法中缓冲流会关闭其包装的底层字节流// BufferedInputStream 的 close() 方法publicvoidclose()throwsIOException{byte[]buffer;while((bufferbuf)!null){if(bufUpdater.compareAndSet(this,buffer,null)){InputStreaminputin;innull;if(input!null)input.close();return;}}}因此我们只需关闭缓冲流对象底层的FileInputStream/FileOutputStream也会随之关闭无需单独处理。缓冲流一次读写一个字节的实现与原理1 ) 代码实现用缓冲流以单字节方式拷贝文件代码极为简洁与基础字节流的写法几乎一致只是将FileInputStream/FileOutputStream包装在缓冲流中。publicclassOutputDemo11{publicstaticvoidmain(String[]args)throwsIOException{// 创建字节缓冲输入流底层自动创建长度为8192的字节数组BufferedInputStreambisnewBufferedInputStream(newFileInputStream(bytestream\\a.avi));// 创建字节缓冲输出流底层自动创建长度为8192的字节数组BufferedOutputStreambosnewBufferedOutputStream(newFileOutputStream(bytestream\\copy.avi));intb;while((bbis.read())!-1){bos.write(b);}// 关闭缓冲流底层字节流也会被关闭bis.close();bos.close();}}2 ) 底层原理与内存模型尽管这里每次还是读写一个字节但缓冲流内部有一个8192字节的数组作为缓冲区其工作流程如下一次读取8192字节每次读出1字节每次写入1字节一次写出8192字节硬盘源文件缓冲输入流内部数组变量 b缓冲输出流内部数组硬盘目标文件关键过程bis.read() 底层会一次性从硬盘读取 8192 字节填充到缓冲输入流的内部数组中。后续调用 read() 时实际是从内存中的数组逐字节读取速度极快。bos.write(b) 先将字节写入缓冲输出流的内部数组当数组满 8192 字节时再一次性地将整个数组写入硬盘。反复循环直到文件拷贝完毕。优势将多次与硬盘的交互合并为少数几次大块传输大幅减少内存与硬盘之间的数据交换次数从而提升性能。缓冲流结合字节数组的高效拷贝1 ) 代码实现在缓冲流的基础上再配合自定义的字节数组可以进一步减少read()和write()方法的调用次数性能更优。publicclassOutputDemo12{publicstaticvoidmain(String[]args)throwsIOException{// 创建字节缓冲输入流BufferedInputStreambisnewBufferedInputStream(newFileInputStream(bytestream\\a.avi));// 创建字节缓冲输出流BufferedOutputStreambosnewBufferedOutputStream(newFileOutputStream(bytestream\\copy.avi));byte[]bytesnewbyte[1024];intlen;while((lenbis.read(bytes))!-1){bos.write(bytes,0,len);}bis.close();bos.close();}}2 ) 与单字节方式的区别此时的read(byte[])和write(byte[], int, int)是以“块”为单位进行读写流程如下一次读取8192字节每次取出1024字节每次写入1024字节一次写出8192字节硬盘源文件缓冲输入流内部数组用户自定义数组 bytes缓冲输出流内部数组硬盘目标文件区别在单字节方式中内存中的“倒手”是一字节一字节进行的循环次数多。在数组方式中每次可以倒手1024或用户自定义长度个字节循环次数大幅减少效率更高。四种拷贝方式对比总结拷贝方式使用的流一次读写单位优点缺点字节流 单字节FileInputStream / FileOutputStream1 字节代码简单极慢每次读写都访问硬盘字节流 字节数组FileInputStream / FileOutputStream自定义数组长度速度较快减少硬盘交互次数需手动管理数组缓冲流 单字节BufferedInputStream / BufferedOutputStream底层 8192 字节无需手动创建数组速度介于两者之间内存倒手次数多缓冲流 字节数组BufferedInputStream / BufferedOutputStream自定义数组长度 底层 8192 字节速度最快开发效率高代码稍复杂项目代码结构本示例的项目结构如下bytestream/ └── src/ └── com/ └── wb/ └── output/ ├── OutputDemo11.java // 缓冲流 - 一次读写一个字节 └── OutputDemo12.java // 缓冲流 - 一次读写一个字节数组完整代码示例OutputDemo11.javapackagecom.wb.output;importjava.io.*;publicclassOutputDemo11{publicstaticvoidmain(String[]args)throwsIOException{// 利用缓冲流拷贝文件一次读写一个字节// 创建一个字节缓冲输入流// 在底层创建了一个默认长度为8192的字节数组BufferedInputStreambisnewBufferedInputStream(newFileInputStream(bytestream\\a.avi));// 创建一个字节缓冲输出流// 在底层也创建了一个默认长度为8192的字节数组BufferedOutputStreambosnewBufferedOutputStream(newFileOutputStream(bytestream\\copy.avi));intb;while((bbis.read())!-1){bos.write(b);}// 方法的底层会把字节流也给关闭bis.close();bos.close();}}OutputDemo12.javapackagecom.wb.output;importjava.io.*;publicclassOutputDemo12{publicstaticvoidmain(String[]args)throwsIOException{// 缓冲流结合数组进行文件拷贝// 创建一个字节缓冲输入流BufferedInputStreambisnewBufferedInputStream(newFileInputStream(bytestream\\a.avi));// 创建一个字节缓冲输出流BufferedOutputStreambosnewBufferedOutputStream(newFileOutputStream(bytestream\\copy.avi));byte[]bytesnewbyte[1024];intlen;while((lenbis.read(bytes))!-1){bos.write(bytes,0,len);}bis.close();bos.close();}}小结字节流可以操作所有类型的文件图片、音频、视频等但直接使用效率较低字节缓冲流通过内置的8192字节数组减少了硬盘与内存的交互次数显著提升读写效率缓冲流不能直接操作文件必须包装一个基础字节流真正干活的是底层字节流根据需求可选择单字节或字节数组的读写方式其中缓冲流 字节数组是推荐的最高效拷贝方式关闭缓冲流时底层字节流会被自动关闭无需重复关闭掌握字节缓冲流是 Java IO 进阶的重要一步也是后续学习字符流、对象流等高级 IO 的基础