IO流的思维导图如下所示:
我们下来对文件、IO流原理及流的分类,节点流(访问文件的、访问数组的、访问管道的)和处理流(缓冲流、对象流、管道流),输入流(InputStream和Reader)和输出流(OutputStream和Writer),properties类进行学习。
一、文件
什么是文件:文件,对我们并不陌生,文件是保存数据的地方,比如大家经常使用的word文档,txt文件,excel文件....都是文件,它既可以保存一张图片,也可以保持视频,声音...
文件流:文件在程序中是以流的形式来操作的
流:数据在数据源(文件)和程序(内存)之间经历的路径
输入流:数据从数据源(文件)到程序(内存)的路径
输出流:数据从程序(内存)到数据源(文件)的路径
二、常用的文件操作:
1.创建文件对象相关构造器和方法
相关方法
new File(String pathname) //根据路径创建一个File对象
new File(File parent,String child) //根据父目录文件+子路径创建
new File(String parent,String child) //根据父目录+子路径构建
createNewFile 创建新文件
我们采用方式一进行创建:
new File(String pathname) //根据路径创建一个File对象
我们的代码如下所示:
@Test //方式1 new File(String pathname) //根据路径创建一个File对象 public void create01(){ String filePath ="e:\\news1.txt"; File file = new File(filePath); try { file.createNewFile(); System.out.println("文件创建成功"); } catch (IOException e) { e.printStackTrace(); }
我们运行之后如下:
我们在E盘里进行查看是否有该文件:
我们发现成功创建
我们采用方式二进行创建如下:
new File(File parent,String child) //根据父目录文件+子路径创建
我们创建代码如下所示:
@Test //方式2 new File(File parent,String child) //根据父目录文件+子路径创建 //e:\\news2.txt public void create02(){ File parentFile = new File("e:\\"); String filename="news.txt"; //这里的file对象,在java程序中,只是一个java对象 //只有执行了createNewFile方法,才会真正的在磁盘创建该文件 File file = new File(parentFile, filename); try { file.createNewFile(); System.out.println("创建成功"); } catch (IOException e) { e.printStackTrace(); } }
我们运行之后如下所示:
我们打开之后如下所示:
我们进行查看方式三:
new File(String parent,String child) //根据父目录+子路径构建
我们编写代码如下所示:
@Test //方式3 new File(String parent,String child) //根据父目录+子路径构建 //e:\\news3.txt public void create03(){ String parentPath="e:\\"; String fileName="new3.txt"; File file = new File(parentPath, fileName); try { file.createNewFile(); System.out.println("文件创建成功"); } catch (IOException e) { e.printStackTrace(); } }
我们运行之后如下所示:
我们找到如下所示:
我们发现当我们的写法如下所示:文件写为/,也可以创建成功,但是我们常用的是\\。
@Test //方式4 new File(String parent,String child) //根据父目录+子路径构建 //e:\\news4.txt public void create03(){ String parentPath="e:/"; String fileName="new4.txt"; File file = new File(parentPath, fileName); try { file.createNewFile(); System.out.println("文件创建成功"); } catch (IOException e) { e.printStackTrace(); } }
运行如下所示:
我们找到如下所示:
2.获取文件信息
getName(获取名字)、getAbsolutePath(获取绝对路径)、getParent(获取父集目录)、length(大小)、exists(是否存在这个文件)、isFile(是否是文件)、isDirectory(是否是目录)。
我们发现File里面有好多方法。
我们进行演示如下所示:
我们编写的代码如下所示:
//获取文件的信息 @Test public void info(){ //先创建文件对象 File file = new File("e:\\news1.txt"); //调用相应的方法得到对应信息即可。 System.out.println("文件名字="+file.getName()); System.out.println("文件绝对路径="+file.getAbsoluteFile()); System.out.println("文件父集目录="+file.getParent()); System.out.println("文件大小(字节)="+file.length()); System.out.println("文件是否是文件:"+file.isFile());//T System.out.println("文件是否存在:"+file.exists());//T System.out.println("文件是否是目录:"+file.isDirectory());//F } }
我们的文件如下所示:
我们的文件内容如下所示:
我们发现该编码为UTF-8编码,UTF-8编码一个英文字母对应一个字节,汉字对应三个字节。
我们运行之后的结果如下所示:
3.目录的操作和文件删除
mkdir创建一级目录,mkdirs创建多级目录,delete删除空目录或文件
(1)判断:e:\\news1.txt 是否存在,如果存在就删除
我们创建代码如下所示:
import org.junit.Test; import java.io.File; import java.io.IOException; public class Directory_ { public static void main(String[] args) { String filepath="e:\\news1.txt"; File file = new File(filepath); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } // 判断:e:\\news1.txt 是否存在,如果存在就删除 @Test public void m1(){ String filepath="e:\\news1.txt"; File file = new File(filepath); if (file.exists()){ if(file.delete()){ System.out.println(filepath+"删除成功"); }else { System.out.println(filepath+"删除失败"); } }else { System.out.println("该文件不存在..."); } } }
我们运行之后如下所示:
我们查看之后如下所示:
我们发现删除成功。
(2) 判断:d:\\demo02是否存在,如果存在就删除,否则提示不存在
我们编写代码如下所示:
// 2.判断:d:\\demo02是否存在,如果存在就删除,否则提示不存在 //这里我们需要体会到,在java编程中,目录也被当作文件 @Test public void m2() { String filepath = "d:\\demo02"; File file = new File(filepath); if (file.exists()) { if (file.delete()) { System.out.println(filepath+"删除成功"); } else { System.out.println(filepath+"删除失败"); } } else { System.out.println("该目录不存在"); } } }
我们运行之后如下所示:
我们在D盘里面进行创建该目录:
我们继续运行之后如下所示:
我们进入D盘里面发现该目录已经被删除了。
(3)判断 D:\\demo\\a\\b\\c 目录是否存在,如果存在就提示已经存在,否则就创建
我们创建多级目录的时候采用mkdirs进行创建,而采用mkdir进行创建的时候则会创建失败。
我们创建的代码如下所示:
@Test public void m3(){ String directorypath="D:\\demo\\a\\b\\c"; File file = new File(directorypath); if(file.exists()){ System.out.println("该目录已经存在"); }else{ if(file.mkdirs()){//创建一级目录使用mkdir(),创建多级目录使用mkdirs() System.out.println(directorypath+"创建成功"); }else { System.out.println(directorypath+"创建失败"); } } }
运行之后如下所示:
我们进入D盘进行查看:
我们用mkdir进行创建如下所示:
我们利用mkdir创建一级目录如下所示:‘
4.IO流原理及流的分类
java IO流原理:
(1)I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输。如读/写文件,网络通讯等。
(2)java程序中,对于数据的输入/输出操作以“流(Stream)"的方式进行。
(3)java.io包下提供了各种”流“类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
(4)输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
(5)输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中
流的分类:
按操作数据单位不同分为:字节流(8bit。效率低一点,但是对于二进制文件(声音文件,视频文件,word文件)可以保证操作为无损操作),字符流(按字符。文本文件)
按数据流的流向不同分为:输入流,输出流
按流的角色的不同分为:节点流,处理流/包装流
(抽象基类) 字节流 字符流 输入流 InputStream Reader 输出流 OutputStream Writer (1)java的IO流共涉及40多个类,实际上非常规则,都是从如上4个抽象基类派生的。
(2)由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
他们为抽象类,不能够直接实例化,它对应的子类对象才能操作。
以下四个都是抽象类:
InputStream、OutputStream、Writer(字符输出流)、Reader(字符输入流)
三、字节流
InputStream:字节输入流
InputStream抽象类是所有类字节输入流的超类
InputStrem常用的子类
1.FileInputStream:文件输入流
2.BufferedInputStream:缓冲字节输入流
3.ObjectInputStream:对象字节输入流
我们的关系图如下所示:
1.FileInputStream:文件输入流
要求使用FileInputStream读取hello.txt文件,并将文件内容显示到控制台。
我们写入如下文件:
我们编写的代码如下所示:
/** * 演示读取hello.txt文件 * 单个字节的读取,效率比较低 * @throws IOException */ @Test public void readFile01() throws IOException { int readData=0; String filePath="e:\\hello.txt"; //创建FileInputStream对象,用于读取文件 FileInputStream fileInputStream = new FileInputStream(filePath); //从该输入流读取一个字节的数据,如果没有输入可用,此方法将阻止。 //如果返回-1,表示读取完毕。 while(( readData=fileInputStream.read()) !=-1){ System.out.print((char)readData);//转成char显示 } //关闭文件流,释放资源 fileInputStream.close(); } }
我们运行之后如下所示:
如果我们用此方法读取中文的时候,会出现乱码。
我们使用read(byte [ ] b )来进行读取。
我们将文件改为如下所示:
我们编写的代码如下所示:
/** * 演示读取hello.txt文件 * 使用read(byte[] b)读取文件,提高效率 * @throws IOException */ @Test public void readFile01() throws IOException { //字节数组 byte[] buf = new byte[24];//一次读取24个字符 int readLen=0; String filePath="e:\\hello.txt"; //创建FileInputStream对象,用于读取文件 FileInputStream fileInputStream = new FileInputStream(filePath); //从该输入流读取最多b.length字节的数据到字节数组,此方法将阻塞,直到某些输入可用 //如果返回-1,表示读取完毕。 //如果读取正常,返回实际读取的字节数 while ((readLen=fileInputStream.read(buf))!=-1){ System.out.print((new String(buf,0,readLen)));//显示 } //关闭文件流,释放资源 fileInputStream.close(); } }
运行之后如下所示:
2.FileOutputStream:文件输出流
请使用FileOutputStream在a.txt文件中写入"hello,world".如果文件不存在,会创建文件(注意:前提是目录已经存在)。
我们的代码设计如下所示:
/** * 演示使用FileOutputStream,将数据写到文件中,如果该文件不存在,则创建 */ @Test public void writeFile(){ //请使用FileOutputStream在a.txt文件中写入"hello,world".如果文件不存在,会创建文件(注意:前提是目录已经存在)。 //创建FileOutputStream对象 String filePath="e:\\a.txt"; FileOutputStream fileOutputStream= null; try { //得到FileOutputStream对象 fileOutputStream = new FileOutputStream(filePath); //写入一个字节 fileOutputStream.write('a'); } catch (IOException e) { e.printStackTrace(); }finally { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
我们运行之后如下所示:
以上代码为写入一个字符,我们下来看写入字符串的实现
我们写入字符串的代码设计如下所示:
@Test public void writeFile(){ //请使用FileOutputStream在a.txt文件中写入"hello,world".如果文件不存在,会创建文件(注意:前提是目录已经存在)。 //创建FileOutputStream对象 String filePath="e:\\a.txt"; FileOutputStream fileOutputStream= null; try { //得到FileOutputStream对象 fileOutputStream = new FileOutputStream(filePath); //写入一个字节 fileOutputStream.write('a'); //写入字符串 String str="hello,world!"; //str.getBytes()可以把 字符串转变为字节数组 fileOutputStream.write(str.getBytes()); /* write(byte[] b,int off,int len) 将len字节从位于偏移量off的指定字节数组写入此文件输出流。 */ String str1="rgf,ypl!"; fileOutputStream.write(str1.getBytes(),0,str1.length()); //写入前三个字节 fileOutputStream.write(str1.getBytes(),0,3); } catch (IOException e) { e.printStackTrace(); }finally { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } }
我们运行之后如下所示:
当我们在前一个写入的基础上再写入新内容的时候,我们会发现会覆盖之前的内容。
代码如下所示:
/** * 演示使用FileOutputStream,将数据写到文件中,如果该文件不存在,则创建 */ @Test public void writeFile(){ //请使用FileOutputStream在a.txt文件中写入"hello,world".如果文件不存在,会创建文件(注意:前提是目录已经存在)。 //创建FileOutputStream对象 String filePath="e:\\a.txt"; FileOutputStream fileOutputStream= null; try { //得到FileOutputStream对象 fileOutputStream = new FileOutputStream(filePath); //写入一个字节 //fileOutputStream.write('a'); //写入字符串 String str="hello,world!"; //str.getBytes()可以把 字符串转变为字节数组 // fileOutputStream.write(str.getBytes()); /* write(byte[] b,int off,int len) 将len字节从位于偏移量off的指定字节数组写入此文件输出流。 */ String str1="rgf,ypl!"; // fileOutputStream.write(str1.getBytes(),0,str1.length()); //写入前三个字节 fileOutputStream.write(str1.getBytes(),0,3); } catch (IOException e) { e.printStackTrace(); }finally { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
我们运行之后如下所示:
我们会发现此运行结果覆盖掉了之前的内容。
我们除了重新创建str之外,我们也可以采用其他方式进行创建。
2.new FileOutputStream(filePath,true),当写入内容时,是追加到文件后面
我们设计的代码如下所示:
@Test public void writeFile(){ //请使用FileOutputStream在a.txt文件中写入"hello,world".如果文件不存在,会创建文件(注意:前提是目录已经存在)。 //创建FileOutputStream对象 String filePath="e:\\a.txt"; FileOutputStream fileOutputStream= null; try { //得到FileOutputStream对象 //说明: //1.new FileOutputStream(filePath)创建方式,当写入内容时,会覆盖原来的内容 //2.new FileOutputStream(filePath,true),当写入内容时,是追加到文件后面 fileOutputStream = new FileOutputStream(filePath,true); //写入一个字节 fileOutputStream.write('a'); //写入字符串 String str="hello,world!"; //str.getBytes()可以把 字符串转变为字节数组 fileOutputStream.write(str.getBytes()); /* write(byte[] b,int off,int len) 将len字节从位于偏移量off的指定字节数组写入此文件输出流。 */ String str1="rgf,ypl!"; fileOutputStream.write(str1.getBytes(),0,str1.length()); //写入前三个字节 fileOutputStream.write(str1.getBytes(),0,3); } catch (IOException e) { e.printStackTrace(); }finally { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } }
我们运行之后如下所示:
我们发现是在之前的文件内容的基础上进行输出的。
3.文件拷贝
我们在D盘里面存储如下照片:
我们设计的原理如下所示:
之后我们将其拷贝到E盘里面,我们进行设计代码:
package com.rgf.outputStream_; import java.io.*; public class FileCopy { public static void main(String[] args) { //完成文件拷贝,将D盘里面的微信图片D:\\微信图片.png拷贝到 E:\\ //思路分析 //1.创建文件的输入流,将文件读入到程序 //2.创建文件的输出流,将读取到的文件数据,写入到指定的文件。 FileInputStream fileInputStream = null; FileOutputStream fileOutputStream = null; String srcFilePath="D:\\微信图片.png"; String destFilePath="E:\\微信图片.png"; try { fileInputStream = new FileInputStream(srcFilePath); fileOutputStream = new FileOutputStream(destFilePath); //定义一个字节数组,提高读取效果 byte[] buf=new byte[1024]; int readLen=0; while ((readLen=fileInputStream.read(buf))!=-1){ //读取到后,就写入到文件,通过FileOutputStream //即,是一边读一边写 fileOutputStream.write(buf,0,readLen); //一定要使用这个方法 } System.out.println("拷贝成功"); } catch (IOException e) { e.printStackTrace(); }finally { //关闭输入流和输出流,释放资源 if(fileInputStream!=null){ try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if(fileOutputStream!=null){ try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
我们运行之后如下所示:
我们打开E盘进行查看:
我们发现成功拷贝进去。
四、字符流
1.文件字符流说明
FileReader和FileWriter是字符流,即按照字符来操作io
FileReader的关系如下所示:
FileReader相关方法:
(1)new FileReader(File/String)
(2)read:每次读取单个字符,返回该字符,如果到文件末尾返回-1。
(3)read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1
相关API:
(1)new String(char[] ):将char[]转换成String
(2)new String(char [],off,len):将char[]的指定部分转换成String
FileWriter的关系如下所示:
FileWriter常用方法:
(1)new FileWriter(File/String):覆盖模式,相当于流的指针在首端
(2)new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
(3)writer(int):写入单个字符
(4)writer(char[]):写入指定数组
(5)writer(char[],off,len):写入指定数组的指定部分
(6)writer(string):写入整个字符串
(7)write(string,off,len):写入字符串的指定部分
相关API:String类:toCharArray:将String转换成char[]
注意:
FileWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件!
2.FileReader
使用FileReader从story.txt读取内容,并显示。
我们的代码设计如下所示:
单个字符进行读取
package com.rgf.reader_; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class FileReader_ { public static void main(String[] args) { FileReader fileReader=null; int data=' '; String filePath="E:\\story.txt"; //1.创建FileReader对象 try { fileReader = new FileReader(filePath); //循环读取 使用read,单个字符读取 while((data=fileReader.read())!=-1){ System.out.print((char) data); } } catch (IOException e) { e.printStackTrace(); }finally { if(fileReader!=null){ try { fileReader.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
我们运行之后如下所示:
以上方法读取速度慢,我们进行尝试如下方法:
我们使用字符数组来进行读取文件:
我们设计的代码如下所示:
//字符数组读取文件 package com.rgf.reader_; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class FileReader_ { public static void main(String[] args) { FileReader fileReader=null; int readLen=0; char[] buf=new char[8]; String filePath="E:\\story.txt"; //1.创建FileReader对象 try { fileReader = new FileReader(filePath); //循环读取 使用read(buf),返回的是实际读取到的字符数 //如果返会回-1,说明到文件结束 while((readLen=fileReader.read(buf))!=-1){ System.out.print(new String(buf,0,readLen)); } } catch (IOException e) { e.printStackTrace(); }finally { if(fileReader!=null){ try { fileReader.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
我们运行之后如下所示:
3.FileWriter
使用FileWriter将“风雨之后,定见彩虹”写入到note.txt文件中,注意细节。
(1)writer(string):写入整个字符串
我们的代码如下所示:
package com.rgf.writer; import java.io.FileWriter; import java.io.IOException; public class FileWriter_ { public static void main(String[] args) { String filePath="E:\\note.txt"; //创建FileWriter对象 FileWriter fileWriter=null; try { fileWriter = new FileWriter(filePath); fileWriter.write("风雨之后,定见彩虹"); } catch (IOException e) { e.printStackTrace(); }finally { } } }
我们发现在我们编写完毕之后没有关闭之后进入如下所示:
之后我们在finally加入如下所示:
package com.rgf.writer; import java.io.FileWriter; import java.io.IOException; public class FileWriter_ { public static void main(String[] args) { String filePath="E:\\note.txt"; //创建FileWriter对象 FileWriter fileWriter=null; try { fileWriter = new FileWriter(filePath); fileWriter.write("风雨之后,定见彩虹"); } catch (IOException e) { e.printStackTrace(); }finally { //对应FileWriter,一定要关闭流,或者flush才能真正的把数据写入到文件 if(fileWriter!=null){ try { fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
运行之后如下所示:
(2)writer(int):写入单个字符
我们设计代码如下所示:
package com.rgf.writer; import java.io.FileWriter; import java.io.IOException; /* (1)writer(int):写入单个字符 (2)writer(char[]):写入指定数组 (3)writer(char[],off,len):写入指定数组的指定部分 (4)writer(string):写入整个字符串 (5)write(string,off,len):写入字符串的指定部分 */ public class FileWriter_ { public static void main(String[] args) { String filePath="E:\\note.txt"; //创建FileWriter对象 FileWriter fileWriter=null; try { fileWriter = new FileWriter(filePath); fileWriter.write('H'); } catch (IOException e) { e.printStackTrace(); }finally { //对应FileWriter,一定要关闭流,或者flush才能真正的把数据写入到文件 if(fileWriter!=null){ try { fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
我们运行之后如下所示:
(3)writer(char[]):写入指定数组
package com.rgf.writer; import java.io.FileWriter; import java.io.IOException; /* (1)writer(int):写入单个字符 (2)writer(char[]):写入指定数组 (3)writer(char[],off,len):写入指定数组的指定部分 (4)writer(string):写入整个字符串 (5)write(string,off,len):写入字符串的指定部分 */ public class FileWriter_ { public static void main(String[] args) { String filePath="E:\\note.txt"; //创建FileWriter对象 FileWriter fileWriter=null; char[] chars={'a','b','c'}; try { fileWriter = new FileWriter(filePath);//默认是覆盖. fileWriter.write(chars); } catch (IOException e) { e.printStackTrace(); }finally { //对应FileWriter,一定要关闭流,或者flush才能真正的把数据写入到文件 if(fileWriter!=null){ try { fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
我们运行之后如下所示:
(4)writer(char[],off,len):写入指定数组的指定部分
我们设计的代码如下所示:
package com.rgf.writer; import java.io.FileWriter; import java.io.IOException; /* (1)writer(int):写入单个字符 (2)writer(char[]):写入指定数组 (3)writer(char[],off,len):写入指定数组的指定部分 (4)writer(string):写入整个字符串 (5)write(string,off,len):写入字符串的指定部分 */ public class FileWriter_ { public static void main(String[] args) { String filePath="E:\\note.txt"; //创建FileWriter对象 FileWriter fileWriter=null; char[] chars={'a','b','c'}; try { fileWriter = new FileWriter(filePath); fileWriter.write(chars); fileWriter.write("争做最棒的程序员".toCharArray(),0,3); //toCharArray(字符数组) } catch (IOException e) { e.printStackTrace(); }finally { //对应FileWriter,一定要关闭流,或者flush才能真正的把数据写入到文件 if(fileWriter!=null){ try { fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
我们运行之后如下所示:
(5)write(string,off,len):写入字符串的指定部分
package com.rgf.writer; import java.io.FileWriter; import java.io.IOException; /* (1)writer(int):写入单个字符 (2)writer(char[]):写入指定数组 (3)writer(char[],off,len):写入指定数组的指定部分 (4)writer(string):写入整个字符串 (5)write(string,off,len):写入字符串的指定部分 */ public class FileWriter_ { public static void main(String[] args) { String filePath="E:\\note.txt"; //创建FileWriter对象 FileWriter fileWriter=null; char[] chars={'a','b','c'}; try { fileWriter = new FileWriter(filePath); fileWriter.write("争做最棒的程序员",0,3);//覆盖 } catch (IOException e) { e.printStackTrace(); }finally { //对应FileWriter,一定要关闭流,或者flush才能真正的把数据写入到文件 if(fileWriter!=null){ try { fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
我们运行之后如下所示:
在数据量大的情况下,可以使用循环操作。
我们可以通过断点的形式来查看close的重要性。
我们继续追进去,我们发现进入了他的父类:OutputStreamWriter
我们进入如下所示:
我们进入如下最重要的地方。
我们发现这才是我们真正添加文件的地方。
我们发现底层用的是FileOutputStream,调用的是底层的方法。
我们继续进入即可发现我们所输出的一系列数据
我们即可看到返回的东西。
我们的flush也可以用同样的方法。
我们继续往进追:
此为判断是否流关闭掉了。
我们发现底层都是按照WriteBytes进行操作的。
我们进入了this.out.write,我们发现底层仍然是FileOutputStream。
我们发现close相当于等价于flush()+一个关闭的行为。
五、节点流和处理流
1.字节流和处理流的原理机制
1.节点流可以从一个特定的数据源(存放数据的地方)读写数据,如FileReader、FileWriter(对文件进行读和对文件进行写的对象
2.处理流(也叫包装流)是“连接”在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,也更加灵活,如BufferedReader、BufferWriter.
BufferedReader类中,有属性Reader,即可以封装一个节点流(该节点流可以是任意的,只要是Reader的一个子类就行)
我们进入他们的源码如下所示:
我们的BufferedReader类继承自Reader,我们的BufferReader可以用Reader的子类。BufferedWriter也是一样的。
我们查看构造器里面也有writer。
节点流和处理流的区别和联系:
1.节点流是底层流/低级流,直接跟数据源相接。
2.处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
3.处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连(模拟修饰器设计模式)
我们下来演示修饰器的效果。
我们利用代码进行演示:
我们新建Reader_:
package com.rgf.writer; public abstract class Reader_ { //抽象类 Reader public void readFile() { }; public void readString() { }; }
新建FileReader_:
package com.rgf.writer; //节点流 public class FileReader_ extends Reader_{ public void readFile(){ System.out.println("对文件进行读取.."); } }
新建StringFile_:
package com.rgf.writer; /** * 节点流 */ public class StringReader_ extends Reader_{ public void readString() { System.out.println("读取字符串"); } }
我们新建BufferedReader_:
package com.rgf.writer; /** * 做成处理流/包装流 */ public class BufferedReader_ extends Reader_ { private Reader_ reader_; //属性是Reader_类型,即我们可以调用里面的方法 //接收Reader_ 子类对象 public BufferedReader_(Reader_ reader_) { this.reader_ = reader_; } //让方法更加灵活,多次读取文件,扩展了readFile() public void readFiles(int num){ for(int i=0;i<num;i++){ reader_.readFile(); } } //扩展readString,批量处理字符串数据 public void readStrings(int num){ for (int i=0;i<num;i++){ reader_.readString(); } } }
我们所编写的BufferedReader_里面包含了FileReader_类里面的readFile方法,StringReader_类里面的readStrings方法。
我们下来进行测试:
package com.rgf.writer; public class Test_ { public static void main(String[] args) { BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader_()); bufferedReader_.readFiles(10); //这次希望通过BufferedReader_ 多次读取字符串 BufferedReader_ bufferedReader_1 = new BufferedReader_((new StringReader_())); bufferedReader_1.readStrings(10); } }
我们运行之后如下所示:
以上的BufferedReader_是我们扩展的方法,我们也可以直接调用我们需要的方法(字节流)
FileReader_ fileReader_ = new FileReader_(); fileReader_.readFile();
我们运行之后如下所示:
我们整个的运行机制就是上述的原理所示,即可通过BufferedReader_更加方便,可以使用更多的单一类的方法,更加高效。
处理流的功能主要体现在以下两个方面:
1.性能的提高:主要以增加缓冲的方式来提高输入输出的效率
2.操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便。
以上演示的过程中,我们采用了多个方法,我们也可以采用统一的read方法进行管理:
public abstract void read();
之后的方法我们设定为:
public void read(){ }
我们利用多态的动态绑定机制来绑定到他对应的子类。
我们后面在调用时,利用对象动态绑定机制,绑定到对应的实现子类即可。
2.处理流BufferedReader
BufferedReader和BufferedWriter属于字符流,是按照字符来读取数据的。关闭处理流,只需要关闭外层流即可。尽量来操作文本文件,而不是二进制文件。二进制文件(图片,声音,视频)在组织的时候是按字节来组织的。文本文件用的是字符,用字符来操作比较高效。
如果用二进制文件来按字符读取,有可能会造成字符的损毁。
我们设置代码如下所示:
package com.rgf.reader_; import java.io.BufferedReader; import java.io.FileReader; /** * 演示BufferedReader使用 */ public class BufferedReader_ { public static void main(String[] args) throws Exception{ String filePath="E:\\a.txt"; //创建bufferedReader对象 BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath)); //读取,bufferedReader读取的时候,底层其实用的是FileReader. String line; //按行读取,效率高 //说明 //1.bufferedReader.readLine()是按行读取文件 //2.当返回null时,表示文件读取完毕 while((line=bufferedReader.readLine())!=null){ System.out.println(line); } //关闭流,这里注意,只需要关闭BufferedReader,因为底层会自动的去关闭节点流 bufferedReader.close(); } }
我们运行之后如下所示:
我们通过断点来查看关闭BufferedReader的底层机制:
我们点红色箭头进入里面。
我们发现此时我们的in已经显示的是FileReader 。
3.处理流BufferedWriter
使用BufferedWriter将“沃尔总冠军”写入到文件中。
我们设计的代码如下所示:
package com.rgf.reader_; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; /** * 演示BufferedWriter的使用 */ public class BufferedWriter_ { public static void main(String[] args) throws IOException { String filePath="E:\\a.txt"; //创建BufferedWriter BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath)); bufferedWriter.write("沃尔总冠军1"); bufferedWriter.write("沃尔总冠军2"); bufferedWriter.write("沃尔总冠军3"); //说明:关闭外层流即可,传入的new FileWriter(filePath)会在底层关闭。 bufferedWriter.close(); } }
我们运行之后如下所示:
我们发现此输入没有换行,我们将代码修改如下所示:
package com.rgf.reader_; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; /** * 演示BufferedWriter的使用 */ public class BufferedWriter_ { public static void main(String[] args) throws IOException { String filePath="E:\\a.txt"; //创建BufferedWriter BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath)); bufferedWriter.write("沃尔总冠军1"); bufferedWriter.newLine(); //插入一个和系统相关的换行 bufferedWriter.write("沃尔总冠军2"); bufferedWriter.newLine(); //插入一个和系统相关的换行 bufferedWriter.write("沃尔总冠军3"); bufferedWriter.newLine(); //插入一个和系统相关的换行 //说明:关闭外层流即可,传入的new FileWriter(filePath)会在底层关闭。 bufferedWriter.close(); } }
我们运行之后如下所示:
我们查看BufferedWriter是否有追加的方法,我们通过构造器查看其方法:
我们发现没有boolean,即没有追加的方法
我们在FileWriter可以发现,即可以进行追加。
我们设计代码如下所示:
package com.rgf.reader_; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; /** * 演示BufferedWriter的使用 */ public class BufferedWriter_ { public static void main(String[] args) throws IOException { String filePath="E:\\a.txt"; //创建BufferedWriter //说明: //1.new FileWriter(filePath,true)表示以追加的方式写入 //2.new FileWriter(filePath),表示以覆盖的方式写入 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath,true)); bufferedWriter.write("沃尔总冠军1"); bufferedWriter.newLine(); //插入一个和系统相关的换行 bufferedWriter.write("沃尔总冠军2"); bufferedWriter.newLine(); //插入一个和系统相关的换行 bufferedWriter.write("沃尔总冠军3"); bufferedWriter.newLine(); //插入一个和系统相关的换行 //说明:关闭外层流即可,传入的new FileWriter(filePath)会在底层关闭。 bufferedWriter.close(); } }
运行之后如下所示:
4.BufferedReader和BufferedWriter完成文本文件拷贝。
我们设计的代码如下所示:
package com.rgf.writer; import java.io.*; public class BufferedCopy_ { public static void main(String[] args) { //说明: //1.BufferedReader和BufferedWriter是安装字符操作 //2.不要去操作二进制文件,可能造成文件损坏 //首先确定要拷贝的文件 String srcFilePath="E:\\a.txt"; //确定要拷贝到的地址 String destFilePath="E:\\b.txt"; BufferedReader br=null; BufferedWriter bw=null; String line; try { br=new BufferedReader(new FileReader(srcFilePath)); bw= new BufferedWriter(new FileWriter(destFilePath)); //然后进行读取 //说明:readLine读取一行内容,但是没有换行 while((line=br.readLine())!=null){ //每读取一行,就写入 bw.write(line); //插入一个换行 bw.newLine(); } } catch (IOException e) { e.printStackTrace(); }finally { //关闭流 //不关闭无法写入文件,写文件肯定是不为null的时候,所以要在不为null时关闭,这样才能写入(不为null说明有文件,在已经有文件的情况下要写入的话需关闭),如果为null的话,是已经关闭了,不需要手动关闭了。 if(br!=null){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if(bw!=null){ try { bw.close(); System.out.println("拷贝完毕.."); } catch (IOException e) { e.printStackTrace(); } } } } }
我们运行之后如下所示:
我们进行拷贝文件的过程为:先确定拷贝的文件,确定拷贝的地址,然后我们new进行读和写的对象,然后进行循环的从被拷贝文件里面读,写到拷贝文件里。
我们拷贝二进制文件如下所示:
package com.rgf.writer; import java.io.*; public class BufferedCopy_ { public static void main(String[] args) { //说明: //1.BufferedReader和BufferedWriter是安装字符操作 //2.不要去操作二进制文件[声音,视频,doc,pdf等等),可能造成文件损坏 //首先确定要拷贝的文件 String srcFilePath="E:\\微信图片.png"; //确定要拷贝到的地址 String destFilePath="E:\\微信.png"; BufferedReader br=null; BufferedWriter bw=null; String line; try { br=new BufferedReader(new FileReader(srcFilePath)); bw= new BufferedWriter(new FileWriter(destFilePath)); //然后进行读取 //说明:readLine读取一行内容,但是没有换行 while((line=br.readLine())!=null){ //每读取一行,就写入 bw.write(line); //插入一个换行 bw.newLine(); } } catch (IOException e) { e.printStackTrace(); }finally { //关闭流 //不关闭无法写入文件,写文件肯定是不为null的时候,所以要在不为null时关闭,这样才能写入(不为null说明有文件,在已经有文件的情况下要写入的话需关闭) if(br!=null){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if(bw!=null){ try { bw.close(); System.out.println("拷贝完毕.."); } catch (IOException e) { e.printStackTrace(); } } } } }
运行之后如下所示:
我们发现拷贝完之后,图片大小也不一样。
我们发现图片已经损坏了。
5.BufferedInputStream和BufferedOutputStream
我们进入源码如下所示:
我们发现BufferedInputStream封装了一个in,但是是在父类FilterInputStream里面,而InputStream是一个抽象类。我们使用BufferedInputStream时,只要使用InputStream子类就可以了。
我们可以发现只要实现了InputStream这种节点流,就可以接收。
BufferedInputStream这种处理流,他的底层用的是从他的父类FilterInputStream继承下来的in这个属性,而且类型是InputStream。而InputStream很多个子类,就可以接收它下面的任意多个子类。
BufferedOutputStream
BufferedOutputStream是字节流,实现缓冲的输出流,可以将多个字节写入底层输出流中,而不必对每次字节写入调用底层系统。它的父类FilterOutputStream也继承了一个属性,out,他的属性是OutoutStream(抽象类),可以接受他的多个实现子类。
6.编程完成图片/音乐的拷贝(要求使用BufferedOutputStream和BufferedInputStream)
我们设计的代码如下所示:
package com.rgf.outputStream_;
/**
* 演示使用BufferedOutputStream和BufferedInputStream
* 使用他们,可以完成二进制文件拷贝
* 思考:字节流可以操作二进制文件,可以操作文本文件。一个中文字符占三个字节,一个英文字符占一个字节。
*/
import java.io.*;
public class BufferedCopy02 {
public static void main(String[] args) {
//首先确定原文件的路径
String srcFilePath="E:\\微信图片.png";
//确定拷贝到的路径
String destFilePath="E:\\复制微信图片.png";
//创建BufferedOutputStream对象和BufferedInputStream对象
BufferedInputStream bis=null;
BufferedOutputStream bos=null;
try {
//因为FileInputStream是InputStream的子类
bis=new BufferedInputStream(new FileInputStream(srcFilePath));
//因为BufferedOutputStream是OutputStream的子类
bos=new BufferedOutputStream(new FileOutputStream(destFilePath));
//循环的读取文件,并写入到destFilePath
byte[] buff =new byte[1024];
int readLen =0;
//当返回-1时,就表示文件读取完毕
while ((readLen=bis.read(buff))!=-1){
bos.write(buff,0,readLen);
}
System.out.println("文件拷贝成功");
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭流,关闭外层的处理流即可,底层会去关闭节点流
if(bis!=null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bos!=null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
我们运行之后如下所示:
我们发现复制过来的大小一致,同时也可以打开,复制成功了。