IO流
作用:用于读写数据的(可以读写文件,或网络中的数据…)
认识IO流

- 字节输入流 InputStream(读字节数据的)
- 字节输出流 OutputStream(写字节数据出去的)

字节流
文件字节输入流(FileInputStream)
作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去


a.txt文件
Java //1.获取文件的字节输入流对象 InputStream in = new FileInputStream("module\\a3.txt"); //2.读取字节数据(一个一个字节读取) /*int r = in.read(); System.out.println((char)r);//a r = in.read(); System.out.println((char)r);//b r = in.read(); System.out.println((char)r);//c r = in.read(); System.out.println((char)r);//肯定不是“你” byte[] bytes = new byte[3]; r = in.read(bytes);//r是读取的字节数组里面存储数据的长度 System.out.println("字节数组读取数据的长度:"+r);//3 System.out.println("读到的数据:"+new String(bytes));//你*/ byte[] bytes = new byte[3]; int len = -1;// 因为读取文件读不到数据就会返回读取长度为-1 while ((len = in.read(bytes))!=-1){ //不能直接打印bytes,有可能bytes残留上一次读取的字节数据,所以本次读取多少长度就转换为字符串对应的长度 //读取len长度,将从0位置开始+读取的字节长度转化为字符串输出 System.out.println("读到的数据:"+new String(bytes,0,len)); } //3.释放资源 in.close(); |
注意事项:
- 使用FilelnputStream每次读取一个字节,读取性能较差,并且读取汉字输出会乱码。
- 使用FilelnputStream每次读取多个字节,读取性能得到了提升,但读取汉字输出还是会乱码。
解决方法:
- 定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。
一次性读完全部字节(不推荐)

注意事项
- 如果文件过大,创建的字节数组也会过大,可能引起内存溢出。
读取文本适合用字符流;字节流适合做数据的转移,比如:文件复制
文件字节输出流(FileOutputStream)
作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去。

Java // 1.获取文件的字节输出流对象 //OutputStream out = new FileOutputStream(new File("module/a5.txt"));//覆盖方式 OutputStream out = new FileOutputStream(new File("module/a5.txt"), true);//追加方式 // 2.输出数据 // 一个字节 out.write('a'); out.write(97); // 字符串=》字节数组 out.write("你好".getBytes()); // 字节数组 byte[] bytes = {97, 98, 99}; out.write(bytes); // 写入换行 out.write('\n'); out.write("\r\n".getBytes());//这个也是换行,推荐使用,windows换行推荐使用\r\n out.write('a'); // 部分输出 out.write("你好abc".getBytes(), 0, 6);//从0的位置,输出6个字节长度 // 3.释放资源 out.close(); |
- 实现写数据的追加操作:创建FileOutpuStream的对象,在构造器第二个参数声明true.
文件复制
- 任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题!
Java //1.获取文件的字节输入流对象 InputStream in = new FileInputStream("源文件地址"); //2.获取文件的字节输出流对象 OutputStream out = new FileOutputStream("新文件地址"); //3.输入流读取数据输出给输出流 byte[] bytes = new byte[1024]; int len = 0;//定义读取字节长度 while((len = in.read(bytes))!=-1){ out.write(bytes,0,len); } //4.释放资源 out.close(); in.close(); System.out.println("复制完成!"); |
资源释放问题
try-catch-finally
Java try{ ... ... }catch(IOException e){ e.printStackTrace(); }finally{ ... } |
- finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止。
- 作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法)。
传统方式:
Java InputStream in = null; OutputStream out = null; try { // 1.获取文件的字节输入流对象 in = new FileInputStream("源文件地址"); // 2.获取文件的字节输出流对象 out = new FileOutputStream("新文件地址"); // 3.输入流读取数据输出给输出流 byte[] bytes = new byte[1024];// 1KB int len = 0;// 定义读取字节长度 while ((len = in.read(bytes)) != -1) { out.write(bytes, 0, len); } System.out.println("复制完成"); // int a = 1/0; } catch (IOException e) { e.printStackTrace();// 给开发人员看 System.out.println("文件复制失败,请重启电脑再试..."); } finally { System.out.println("finally运行了"); // finally无论try里面的代码是否出现异常,finally里面的代码都会运行 try { if (in != null) in.close(); } catch (IOException e) { e.printStackTrace(); } try { if (out != null) out.close(); } catch (IOException e) { e.printStackTrace(); } } |
try-catch:
- try-with-resources释放资源,从jdk7开始有的语法
- 原理:try和catch运行完会自动调用资源的close方法(AutoCloseable接口的方法,每个资源都实现了这个接口重写了close方法)
Java //try-with-resources释放资源,从jdk7开始有的语法 // 原理:try和catch运行完会自动调用资源的close方法(AutoCloseable接口的方法,每个资源都实现了这个接口重写了close方法) // 优点:代码优雅,释放资源失败会抛出异常 try ( // 1.获取文件的字节输入流对象 InputStream in = new FileInputStream("源文件地址"); // 2.获取文件的字节输出流对象 OutputStream out = new FileOutputStream("新文件地址"); // 定义自己的资源,也会自动调用close方法 MyResource mr = new MyResource(); ){ //3.输入流读取数据输出给输出流 byte[] bytes = new byte[1024];//1KB int len = 0;//定义读取字节的长度 while((len = in.read(bytes))!=-1){ out.write(bytes,0,len); } System.out.println("复制完成"); }catch(Exception e){ e.printStackTrace(); System.out.println("失败,请重启电脑"); } |
字符流
文件字符输入流(FileReader)
作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去。

Java try (//1.获取文件的字符输入流对象 Reader in = new FileReader(new File("module\\a5.txt")) ){ //2.读取字符数据并打印 // int c = in.read(); // System.out.println((char)c);//a // // c = in.read(); // System.out.println((char)c);//b // // c = in.read(); // System.out.println((char)c);//c char[] chars = new char[10]; int len = -1;//定义读取字符数组的长度,如果读取不到返回-1 while((len = in.read(chars))!=-1) { //将读取到的数据输出,注意读取多少长度,输出多少长度,否则会有残留数据输出 System.out.print(new String(chars, 0, len));//注意不要换行,因为文本文件里面有换行 } }//3.释放资源 catch (Exception e){ e.printStackTrace(); } |
文件字符输出流(FileWriter)
作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去。

Java try (// 1.获取文件的字符输出流对象 Writer out = new FileWriter(new File("module/a6.txt")); ) { //2.使用字符输出流写数据 out.write(97);//a out.write("你好");//你好 out.write("帅气das", 0, 2);//帅气 out.write(new char[]{97,'b','c','尚'}); System.out.println("\r\n"); out.write(new char[]{97,'b','c','尚'}, 0, 2);//ab out.flush();//刷新流,确保数据写入到文件(输出流可能有部分数据缓存没有写入文件,这样确保缓存数据也写入文件) //刷新后继续可以输出数据,注意流关闭后不能再使用流 out.write("\nhello world"); //注意:close方法会先调用flush刷新方法,再进行关闭 } catch (Exception e) { throw new RuntimeException(e); } |
字符输出输出流的注意实现:字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效。

缓冲流
缓冲字节流
BufferedInputStream缓冲字节输入流和BufferedInputStream缓冲字节输入流
- 原理:缓冲字节输入流自带了8KB缓冲池;缓冲字节输出流也自带了8KB缓冲池

Java try( //1.获取文件的字节输入流对象 FileInputStream in = new FileInputStream("源文件地址"); //缓冲字节输入流对象 BufferedInputStream bufferedInputStream = new BufferedInputStream(in); //2.获取文件的字节输出流对象 FileOutputStream out = new FileOutputStream("新文件地址"); //缓冲字节输出流对象 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out); ){ // 3.输入流读取数据输出给输出流 byte[] bytes = new byte[1024];//1KB int len = 0;//定义读取字节的长度 while((len = bufferedInputStream.read(bytes))!=-1){ bufferedOutputStream.write(bytes,0,len); } System.out.println("复制完成"); }catch (Exception e){ e.printStackTrace(); } |
缓冲字符流
BufferedReader缓冲字符输入流
作用:自带8K(8192)的字符缓冲池,可以提高字符输入流读取字符数据的性能。

字符缓冲输入流新增的功能:按照行读取字符

BufferedWriter缓冲字符输出流
作用:自带8K的字符缓冲池,可以提高字符输出流写字符数据的性能。

字符缓冲输出流新增的功能:换行

Java try ( //1.获取文件的字符输入流对象 Reader in = new FileReader(new File("day03-file-io\\a4.txt")); //缓冲字符输入流对象 BufferedReader bufferedReader = new BufferedReader(in); //2.获取文件的字符输出流对象 FileWriter out = new FileWriter(new File("day03-file-io\\a5.txt")); //缓冲字符输出流对象 BufferedWriter bufferedWriter = new BufferedWriter(out); ){ //3.输入流读取数据输出给输出流 String content = null; //bufferedReader.readLine() 只是读取每一行的数据(不包含换行) while((content = bufferedReader.readLine())!=null){ bufferedWriter.write(content);//只是输出数据 //换行 bufferedWriter.newLine();//注意:输出的时候手动添加换行 } }catch(Exception e){ e.printStackTrace(); } |
性能分析
原始流、缓冲流的性能分析
测试用例:
- 分别使用原始的字节流,以及字节缓冲流复制一个很大视频。
测试步骤:
- 使用低级的字节流按照一个一个字节的形式复制文件。
- 使用低级的字节流按照字节数组的形式复制文件。
- 使用高级的缓冲字节流按照一个一个字节的形式复制文件。
- 使用高级的缓冲字节流按照字节数组的形式复制文件。
Java private static final String SRC_FILE = "想要复制的文件"; private static final String DEST_FILE = "D:\\Test"; public static void main(String[] args) { // 目标:缓冲流,低级流的性能分析。 // copy01(); // 低级的字节流一个一个字节复制:慢的简直无法忍受,直接淘汰,禁止使用!! // copy02(); // 使用低级的字节流按照字节数组的形式复制文件: 相对较慢!可以接受 // copy03(); // 使用高级的缓冲字节流按照一个一个字节的形式复制文件:超级慢,直接淘汰 copy04(); // 使用高级的缓冲字节流按照一个一个字节数组的形式复制文件:极快,推荐使用。 } // 使用低级的字节流按照一个一个字节的形式复制文件。 public static void copy01(){ long start = System.currentTimeMillis(); // 开始时间:返回的是 1970-1-1 0:0:0 走到此刻的总毫秒值。 1s = 1000ms try ( // 1、创建字节输入流管道与源文件接通 InputStream is = new FileInputStream(SRC_FILE); // 2、创建字节输出流管道与目标文件接通 OutputStream os = new FileOutputStream(DEST_FILE + "1.wmv"); ){ // 3、开始一个一个字节的转移数据 int b; while ((b = is.read()) != -1){ os.write(b); } }catch (Exception e){ e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("低级的字节流按照一个一个字节的形式复制文件耗时:" + (end - start) / 1000.0 + "s"); } // 使用低级的字节流按照字节数组的形式复制文件。 public static void copy02(){ long start = System.currentTimeMillis(); // 开始时间:返回的是 1970-1-1 0:0:0 走到此刻的总毫秒值。 1s = 1000ms try ( // 1、创建字节输入流管道与源文件接通 InputStream is = new FileInputStream(SRC_FILE); // 2、创建字节输出流管道与目标文件接通 OutputStream os = new FileOutputStream(DEST_FILE + "2.wmv"); ){ // 3、开始一个一个字节数组的转移数据 byte[] buffer = new byte[1024*8]; int len; while ((len = is.read(buffer)) != -1){ os.write(buffer, 0, len); } }catch (Exception e){ e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("低级的字节数组流按照一个一个字节数组的形式复制文件耗时:" + (end - start) / 1000.0 + "s"); } //使用高级的缓冲字节流按照一个一个字节的形式复制文件。 public static void copy03(){ long start = System.currentTimeMillis(); // 开始时间:返回的是 1970-1-1 0:0:0 走到此刻的总毫秒值。 1s = 1000ms try ( // 1、创建字节输入流管道与源文件接通 InputStream is = new FileInputStream(SRC_FILE); BufferedInputStream bis = new BufferedInputStream(is); // 2、创建字节输出流管道与目标文件接通 OutputStream os = new FileOutputStream(DEST_FILE + "3.wmv"); BufferedOutputStream bos = new BufferedOutputStream(os); ){ // 3、开始一个一个字节的转移数据 int b; while ((b = bis.read()) != -1){ bos.write(b); } }catch (Exception e){ e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("高级的缓冲字节流按照一个一个字节的形式复制文件耗时:" + (end - start) / 1000.0 + "s"); } //使用高级的缓冲字节流按照字节数组的形式复制文件。 public static void copy04(){ long start = System.currentTimeMillis(); // 开始时间:返回的是 1970-1-1 0:0:0 走到此刻的总毫秒值。 1s = 1000ms try ( // 1、创建字节输入流管道与源文件接通 InputStream is = new FileInputStream(SRC_FILE); BufferedInputStream bis = new BufferedInputStream(is); // 2、创建字节输出流管道与目标文件接通 OutputStream os = new FileOutputStream(DEST_FILE + "4.wmv"); BufferedOutputStream bos = new BufferedOutputStream(os); ){ // 3、开始一个一个字节数组的转移数据 byte[] buffer = new byte[1024 * 256]; int len; while ((len = bis.read(buffer)) != -1){ bos.write(buffer, 0, len); } }catch (Exception e){ e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("高级的的缓冲字节流按照一个一个字节数组的形式复制文件耗时:" + (end - start) / 1000.0 + "s"); } |
因此,建议使用字节缓冲输入流、字节缓冲输出流,结合字节数组的方式,目前来看是性能最优的组合。
其他流
字符输入转换流(InputStreamReader)[重点]
解决不同编码时,字符流读取文本内容乱码的问题。
解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了

Java //当前文件 Test01.java(utf-8)使用的 指明以GBK码表读取at.txt(GBK)文件不会乱码 try( //1.定义字节输入流(原始流,里面的字节没有被破坏) InputStream inputStream = new FileInputStream("module\\a6.txt"); //2.定义字符输入转换流 InputStreamReader gbk = new InputStreamReader(inputStream,"GBK"); //3.缓冲流提供性能 BufferedReader br = new BufferedReader(gbk); ){ char[] chs = new char[1024]; int len = -1; while((len = br.read(chs))!=-1){ System.out.println(new String(chs,0,len)); } }catch(Exception e){ e.printStackTrace(); } |
打印流(PrintStream和PrintWriter)
作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。
源码中可以看到里面有缓冲流,所以高效。
例子:97---》a,用打印流就不会转化成a。
PrintStream


Java try( //1.定义输出流(注意不要多态,否则无法使用独有方法) PrintStream printStream = new PrintStream("module\\a7.txt"); ){ printStream.println("hello"); printStream.println(100); printStream.println(true); printStream.println(3.14); printStream.println('a'); }catch(Exception e){ e.printStackTrace(); } |
PrintWriter


Java try( //1.定义输出流(注意不要多态,否则无法使用独有方法) PrintWriter printWriter = new PrintWriter("module/a8.txt"); ){ printWriter.println("hello"); printWriter.println(100); printWriter.println(true); printWriter.println(3.14); printWriter.println('a'); }catch(Exception e){ e.printStackTrace(); } |
PrintStream和PrintWriter的区别
- 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
- PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法。
- PrintWriter继承自字符输出流Writer,因此支持写字符数据出去。
- tomcat(web)服务器响应数据就是使用这个PrintWriter输出流。
特殊数据流(通信时用到)[了解即可]
DataOutputStream(数据输出流)
允许把数据和其类型一并写出去。

Java try ( //1.定义资源 FileOutputStream fileOutputStream = new FileOutputStream("module\\a9.txt"); //2.使用特殊数据输出流写出数据 DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); ){ dataOutputStream.writeByte(100); dataOutputStream.writeChar('a'); dataOutputStream.writeDouble(3.14); dataOutputStream.writeUTF("黑马程序员"); }catch(Exception e){ e.printStackTrace(); } |
DataInputStream(数据输入流)
用于读取数据输出流写出去的数据。

Java try( //1..定义资源 FileInputStream fileInputStream = new FileInputStream("module\\a9.txt"); //2.使用特殊数据输入流 DataInputStream dataInputStream = new DataInputStream(fileInputStream); ){ //注意:一定要按照写的顺序读取 int a = dataInputStream.readByte(); char b = dataInputStream.readChar(); double c = dataInputStream.readDouble(); String d = dataInputStream.readUTF(); System.out.println(a); System.out.println(b); System.out.println(c); System.out.println(d); }catch (Exception e){ e.printStackTrace(); } |
IO框架
- 框架(Framework)是一个预先写好的代码库或一组工具,旨在简化和加速开发过程
- 框架的形式:一般是把类、接口等编译成class形式,再压缩成一个.jar结尾的文件发行出去。
- 封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等。
什么是IO框架?
- 封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等。

Java //commons-io包下的IO流框架的使用 //1.一句代码文件复制 FileUtils.copyFile( new File("源文件地址"), new File("新文件地址") ); //2.一句代码文件夹复制 FileUtils.copyDirectory( new File("源文件夹地址"), new File("新文件夹地址") ); //3.一句代码删除非空文件夹 FileUtils.deleteDirectory(new File("文件夹地址")); //4.jdk7提供新的方式一句代码文件复制 Files.copy(Path.of("源文件地址"), Path.of("新文件地址")); //5.IOUtils IOUtils.copy(new FileInputStream("源文件地址"), new FileOutputStream("新文件地址")); |