(转)教你完全理解IO流里的 read(),read(byte[]),read(byte[],int off,int len)以及write
背景:对于IO部分,总是感觉很虚,不能很好的理解其中的要义,其实仔细分析,掌握其中的规律,一切都会看起来十分的自然。
1 理解
1.1 从头总结
长期以来,java中的InputStream OutputStream write read 傻傻的分不清,这次要彻底的捋一捋。
其实上述四者之间的关系,是可以从内存和硬盘两个角度理解:
对于流,站在内存角度考虑,箭头指向内存就是InputStream输入流,箭头由内存指向硬盘就是OutputStream就是输出流。
对于读写,站在硬盘的角度考虑,箭头指向硬盘就是写入,箭头从硬盘指向内存就是读出。
摘抄下《疯狂java讲义》中的相关内容:
输入流:只能从中读取数据,而不能向其写出数据。
输出流:只能向其写出数据,而不能从中读取数据。
虽然很绕,但是站在内存硬盘,两个角度考虑,就很清晰了。
好的我们先来讲它们的作用,然后再用代码来实现给大家看
read():
1.从读取流读取的是一个一个字节
2.返回的是字节的(0-255)内的字节值
3.读一个下次就自动到下一个,如果碰到-1说明没有值了.
read(byte[] bytes)
1.从读取流读取一定数量的字节,如果比如文件总共是102个字节
2.我们定义的数组长度是10,那么默认前面10次都是读取10个长度
3.最后一次不够十个,那么读取的是2个
4.这十一次,每次都是放入10个长度的数组.
read(byte[] bytes,int off ,int len)
1.从读取流读取一定数量的字节,如果比如文件总共是102个字节
2.我们定义的数组长度是10,但是这里我们写read(bytes,0,9)那么每次往里面添加的(将只会是9个长度),就要读12次,最后一次放入3个.
3.所以一般读取流都不用这个而是用上一个方法:read(byte[]);
下面讲解write
write(int i);
直接往流写入字节形式的(0-255)int值.
write(byte[] bytes);
往流里边写入缓冲字节数组中的所有内容,不满整个数组长度的”空余内容”也会加入,这个下面重点讲,
write(byte[] bytes,int off,int len);
1.这个是更严谨的写法,在外部定义len,然后每次len(为的是最后一次的细节长度)都等于流往数组中存放的长度(可以保证每次都输出读取的内容)
2.如上述read(bytes),前面每次都放入十个,第十一次放入的是2个,如果用第二种write(bytes),将会写入输出流十一次,每次写入十个长度,造成后面有8个空的,比原来的内容多了
3.所以用write(byte[] bytes,int off,int len);就不会出现多出来的空的情况,因为最后一次len不同
下面是详细的代码
public class Test{ public static void main(String[] args) throws Exception { UseTimeTool.getInstance().start(); FileInputStream fis = new FileInputStream("D:\\1.mp3"); FileOutputStream fos = new FileOutputStream("D:\\1copy.mp3"); //(PS:一下3个大家分开来写和测试,为了方便我都列出来了) /*--------------不使用缓冲--------------*/ //如果不缓冲,花了差不多14"秒" int len = -1; while ((len = fis.read()) != -1) { //这里就不是长度的问题了,而是读取的字节"内容",读到一个写一个,相当慢. System.out.println("len : "+ len); fos.write(len); } /*--------------使用缓冲--------------*/ //缓冲方法复制歌曲用了不到20"毫秒" //创建一个长度为1024的字节数组,每次都读取5kb,目的是缓存,如果不用缓冲区,用fis.read(),就会效率低,一个一个读字节,缓冲区是一次读5000个 byte[] bytes = new byte[1024*5]; //每次都是从读取流中读取(5k)长度的数据,然后再写到文件去(5k的)数据,注意,每次读取read都会不同,是获取到下一个,直到后面最后一个. while (fis.read(bytes)!=-1) { //write是最追加到文件后面,所以直接每次添5K. fos.write(bytes); } /*--------------解释len--------------*/ //告诉你为什么用len byte[] bytes = new byte[1024*5]; int len = -1; //解释这个fis.read(bytes)的意思:从读取流"读取数组长度"的数据(打印len可知),并放入数组 while ((len = fis.read(bytes,0,1024)) != -1) { //虽然数组长度的*5,但是这里我们设置了1024所以每次输出1024 System.out.println("len : "+ len); //因为每次得到的是新的数组,所以每次都是新数组的"0-len" fos.write(bytes,0,len); } fis.close(); fos.close(); UseTimeTool.getInstance().stop(); }
为了方便大家,也给大家一个统计时间的工具类
class UseTimeTool { private static UseTimeTool utt = new UseTimeTool(); private UseTimeTool() { } public static UseTimeTool getInstance() { return utt; } private long start; public void start() { start = System.currentTimeMillis(); } public void stop() { long end = System.currentTimeMillis(); System.out.println("所用時間 : " + (end - start) + "毫秒"); } }
好了最后一个:len问题 最后多出数组不满的部分我特再写一个出来给大家分析
首先,文本的内容是
public class Test{ public static void main(String[] args) throws Exception { UseTimeTool.getInstance().start(); FileInputStream fis = new FileInputStream("D:\\a.txt"); FileOutputStream fos = new FileOutputStream("D:\\acopy.txt");
不使用len:
byte[] bytes = new byte[1024*5]; while (fis.read(bytes)!=-1) { fos.write(bytes); }
得到的效果:
发现后续后很多的空部分,所以说不严谨
ps:上述是数组在初始时候未被占满的情况;
如果倒数第二次循环时候数组是占满的,写入时候使用fos.write(bytes);,那么在最后一次fos.write(bytes)时候就会出现数组前一部分是最后读取的内容,后一部分是倒数第二次数组中存在的内容。原因是数组中的内容并不会每次都清空,只会在原来的基础上进行覆盖。所以从严谨的角度出发,推荐第二种写法——
使用len:
byte[] bytes = new byte[1024*5]; int len = -1; while ((len = fis.read(bytes,0,1024)) != -1) { fos.write(bytes,0,len); }
得到的效果
和原来一模一样,讲了那么多就是希望能帮助大家真正的理解.
1
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,让更多的人能够享受到获取知识的快乐!因为本人初入职场,鉴于自身阅历有限,所以本博客内容大部分来源于网络中已有知识的汇总,欢迎各位转载,评论,大家一起学习进步!如有侵权,请及时和我联系,切实维护您的权益!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
2015-08-07 (转)JAVA排序汇总
2015-08-07 (转)Java线程:大总结
2015-08-07 (转)Java线程:新特征-原子量,障碍器
2015-08-07 (转)Java线程:新特征-条件变量
2015-08-07 (转)新特征-信号量
2015-08-07 (转)JAVA新特征
2015-08-07 (转)Java线程:新特征-线程池