五、IO流问题
5.1 关于直接基于“文件夹/目录”创建IO流对象是错误会报java.io.FileNotFoundException: d: download (拒绝访问。)
代码使用演示:
@Test public void test11() throws IOException{ FileInputStream fis = new FileInputStream("d:\\download"); //java.io.FileNotFoundException: d: download (拒绝访问)文件IO必须基于文件路径创建对象 // "d:lldownload"这个是一个文件夹的路径,不是一个文件的路径 FileOutputStream fos = new FileOutputStream( "d:\\download"); //java.io.FileNotFoundException: d: download (拒绝访问) //就算是让 FiLeOutputStream帮你自动创建文件,也必须在IO流的构造器中,指明文件的名称。 }
5.2 输出流如果不能及时关闭(因为可能后面还要用),但是又想要数据及时输出,可以调用flush方法。
但是如果后面不用了,一定要close。因为close不仅仅是刷数据的问题,还涉及内存回收问题。
代码使用演示:
@Test public void test03() throws IOException{ FileWriter fw=new FileWriter("1.txt"); fw.write("天地有正道"); }
🔔注意:
像Filewriter等很多的输出流的内部有自己的一个小小缓冲区。它在调用write方法时,会先将数据写到缓冲区。当缓冲区满的时候,会自动“溢出”到文件。当缓冲区没满的时候,调用close 方法执行时,会把缓冲区的数据“清空”输出。
如果希望数据及时写出,但是暂时还不close,可以使用flush()方法刷新缓存区
六、缓冲流
6.1 分类
缓冲流又称高效流,按照数据类型分类:
- 字节缓冲流:
BufferedInputStream
,BufferedOutputStream
- 字符缓冲流:
BufferedReader
,BufferedWriter
🤖缓冲流的基本原理:
是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
6.2 构造方法
public BufferedInputStream(InputStream in)
:创建一个 新的缓冲输入流。public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。
🔔注意:
缓冲流构造方法中必须包含字节输入流【InputStream】或字节输出流【OutputStream 】对象,才行
代码使用演示:
// 创建字节缓冲输入流 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("ab.txt")); // 创建字节缓冲输出流 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("ac.txt"));
public BufferedReader(Reader in)
:创建一个 新的缓冲输入流。public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流。
代码使用演示:
// 创建字符缓冲输入流 BufferedReader br = new BufferedReader(new FileReader("12.txt")); // 创建字符缓冲输出流 BufferedWriter bw = new BufferedWriter(new FileWriter("13.txt"));
6.3 使用缓冲流前后耗时对比
代码使用演示:
@Test //不使用缓冲流 public void test04() throws IOException{ long start=System.currentTimeMillis(); FileInputStream fis=new FileInputStream("D:\\BaiduNetdiskDownload\\尚硅谷2022Java\\01-Java基础【完结】\\尚硅谷_JavaEE_01_JavaSE课程资料\\01_sofeware\\IDE\\ideaIU-Ultimate-2019.2.3.zip"); FileOutputStream fos=new FileOutputStream("D:\\ideaIU-Ultimate-2019.2.3-副本.zip"); byte[] data=new byte[1024]; int len; while ((len=fis.read(data))!=-1){ fos.write(data,0,len); } fis.close(); fos.close(); long end=System.currentTimeMillis(); System.out.println("耗时:"+(end-start)+"毫秒");//耗时:6200毫秒 } @Test //使用缓冲流 public void test05() throws IOException{ long start=System.currentTimeMillis(); FileInputStream fis=new FileInputStream("D:\\BaiduNetdiskDownload\\尚硅谷2022Java\\01-Java基础【完结】\\尚硅谷_JavaEE_01_JavaSE课程资料\\01_sofeware\\IDE\\ideaIU-Ultimate-2019.2.3.zip"); FileOutputStream fos=new FileOutputStream("D:\\ideaIU-Ultimate-2019.2.3-副本1.zip"); BufferedInputStream bis=new BufferedInputStream(fis); BufferedOutputStream bos=new BufferedOutputStream(fos); byte[] data=new byte[1024]; int len; while ((len=bis.read(data))!=-1){ bos.write(data,0,len); } fis.close(); fos.close(); long end=System.currentTimeMillis(); System.out.println("耗时:"+(end-start)+"毫秒");//耗时:1778毫秒 }
🤮OMG!
不使用缓冲流耗时:6200毫秒;
使用缓冲流耗时:1778毫秒
我们可以很明确的看到,在复制文件时,尤其时很大的文件时,缓冲流真的
非常高效!
why?
原理剖析如下:
6.4 字符缓冲流特有方法
字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,让我们来看它们具备的特有方法。
- BufferedReader:
public String readLine()
: 读一行文字。 - BufferedWriter:
public void newLine()
: 写一行行分隔符,由系统属性定义符号。
代码使用演示:
@Test public void test12() throws IOException{ // 创建流对象 BufferedReader br = new BufferedReader(new FileReader("in.txt")); // 定义字符串,保存读取的一行文字 String line; // 循环读取,读取到最后返回null while ((line = br.readLine())!=null) { System.out.println(line); } // 释放资源 br.close(); }
@Test public void testNewLine()throws IOException{ // 创建流对象 BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt")); // 写出数据 bw.write("你"); // 写出换行 bw.newLine(); bw.write("好"); bw.newLine(); bw.write("吗"); bw.newLine(); // 释放资源 bw.close(); }
七、IO流的关闭问题
IO流的关闭顺序有什么问题?
莫急,请看下面的代码:
@Test public void test06() throws IOException{ long start=System.currentTimeMillis(); FileInputStream fis=new FileInputStream("D:\\BaiduNetdiskDownload\\尚硅谷2022Java\\01-Java基础【完结】\\尚硅谷_JavaEE_01_JavaSE课程资料\\01_sofeware\\IDE\\ideaIU-Ultimate-2019.2.3.zip"); FileOutputStream fos=new FileOutputStream("D:\\ideaIU-Ultimate-2019.2.3-副本2.zip"); BufferedInputStream bis=new BufferedInputStream(fis); BufferedOutputStream bos=new BufferedOutputStream(fos); byte[] data=new byte[1024]; int len; while ((len=bis.read(data))!=-1){ bos.write(data,0,len); } fis.close(); fos.close(); bis.close(); bos.close(); //java.io.IOException: Stream Closed long end=System.currentTimeMillis(); System.out.println("耗时:"+(end-start)+"毫秒");//耗时:1778毫秒 }
why?
为社么会出现“stream closed”?
🤮原因:
IO流有依赖关系。外层的包装流依赖于内存的被包装流。 比如: BufferedInputStream 依赖于 FileInputStream BufferedOutputstream 依赖于 FileOutputstream
如果把内层的IO先关闭了,外层IO流就失去了依赖,就会报错。 比喻:我坐在登子上,我依赖于凳子,如果把登子先撤了,我就会摔倒。
先让我站起来,然后撤登子。
关闭的顺序是: 先关闭外层的包装流,再关闭内层的被包装流
🔔本质:
这段代码数据的流向: 源文件 ->fis ->bis ->data数组 ->bos ->fos
->目标文件BufferedOutputStream和FileOutputStream,如果把fos先关了,bos中的缓冲的数据没有办法完全写出。因为close时,会清空缓冲区
好记: fis被包装在里面的,比喻成内衣,先穿内衣,再穿外衣bis相当于外套。 fos是内衣,bos是外套 关闭好比是脱衣服。
先脱外套,再脱内衣。
八、转换流
8.1 编码与解码
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。基于某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。
比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
乱码问题举例如下:
如何解决乱码问题?
请看下面的类,就是解决乱码问题
8.2 InputStreamReader类
转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
InputStreamReader(InputStream in)
: 创建一个使用默认字符集的字符流。InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流。
解决方案代码如下:
@Test public void test08() throws IOException{ FileInputStream fr=new FileInputStream("1.txt"); InputStreamReader isr=new InputStreamReader(fr,"GBK");//文件的编码格式是GBK,用GBK来解文件的编码 char[] data=new char[10]; int len; while ((len=isr.read(data))!=-1){ System.out.println(new String(data,0,len)); } fr.close(); }
8.3 OutputStreamWriter类
转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
OutputStreamWriter(OutputStream in)
: 创建一个使用默认字符集的字符流。OutputStreamWriter(OutputStream in, String charsetName)
: 创建一个指定字符集的字符流。
8.4 案例
8.4.1 案例①
🔔输出”你太棒了“在GBk格式的1.txt文件中
代码演示如下:
@Test public void test09() throws IOException{ //案例:输出”你太棒了“在GBk格式的1.txt文件中 FileOutputStream fos=new FileOutputStream("1.txt",true); OutputStreamWriter isr=new OutputStreamWriter(fos,"GBK");//把字符数据用GBk的格式转换为字节数据写入1.txt文件中 isr.write("你太棒了"); isr.close(); fos.close(); }
8.4.2 案例②
🔔案例需求不变,把缓冲流加入进去
代码演示如下:
@Test public void test010() throws IOException{ //案例:输出”你太棒了“在GBk格式的1.txt文件中 //将缓冲流加入进去 FileOutputStream fos=new FileOutputStream("1.txt",true); //字节流 BufferedOutputStream bos=new BufferedOutputStream(fos);//缓冲字节流包含字节流 OutputStreamWriter isr=new OutputStreamWriter(fos,"GBK");//字符流 把字符数据转换为GBk格式的数据写入1.txt文件中 BufferedWriter bw=new BufferedWriter(isr);//缓冲字符流包含字符流 isr.write("你太棒了1"); bw.close(); bos.close(); isr.close(); fos.close(); }
8.4.3 案例③
🔔 将编码为GBK的”file_gbk.txt"转换为编码为utf-8的文件”file_utf8.txt"
代码演示如下:
@Test public void test11() throws IOException{ //案例:将编码为GBK的”file_gbk.txt"转换为编码为utf-8的文件”file_utf8.txt" FileInputStream fis=new FileInputStream("file_gbk.txt"); //字节输入流 InputStreamReader isr=new InputStreamReader(fis,"GBK");//字符输入流 BufferedReader br=new BufferedReader(isr);//缓冲字符输入流 String readLine; FileOutputStream fos=new FileOutputStream("file_utf8.txt"); //字节输出流 OutputStreamWriter osw=new OutputStreamWriter(fos,"utf-8"); //字符输出流 BufferedWriter bw=new BufferedWriter(osw); //缓冲字符输出流 while ((readLine=br.readLine())!=null){ bw.write(readLine); bw.newLine(); } bw.close(); osw.close(); fos.close(); br.close(); isr.close(); fis.close(); }
8.5 转换流原理
原理图像如下: