本文共 23480 字,大约阅读时间需要 78 分钟。
Java中把输入/输出(input/output)操作称为流(Stream)。
流:即为起点到接收点有序的数据序列。
流的分类:
1.按照流的方向分为:
输入流:只读,只会从流中读取数据;
输出流:只写,只会向流中写入数据;
(这里记录一下对输入流是读数据,输出流是写数据的理解:这里是站在内存的角度来理解的,输入流也就是数据从目标文件到内存,也就是内存去读目标文件的数据;输出流也就是数据从内存到目标文件,也就是将数据从内存中写到目标文件中。)
2.按照处理的数据分:
字节流(InputStream/OutputStream):读或写的时候以字节为单位;
字符流(Reader/Writer):读或写的时候以字符为单位;
一般情况下(排除特殊情况),一个汉字占一个字符,一个字符占两个字节,如果读或写的时候只读或写一个字节的话,那么就会出现乱码。
3.按照功能分为:
低级流:直接从数据的源头读取数据或者直接把数据写到目标位置的流,称为低级流,也称为节点流;
高级流:对一个已经存在的流的连接和封装,称为高级流,也称为处理流;
先从字节流中的FileInputStream和FileOutputStream说起:以下是两者的模型
代码如下:
package com.Jevin.io;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;public class ByteStreamTest { public static void main(String[] args) { //readFile1(); //readFile2(); //writeFile1(); writeFile2(); } /** * 用FileInputStream读取文件的内容,一个一个字节的读取(出现中文乱码问题) */ public static void readFile1() { FileInputStream fin = null; try { //文件的完整路径有三部分组成:路径名称+分隔符+文件名称; //如果指定文件不存在,或者他是一个目录,而不是一个常规文件,抑或因为其他原因无法打开进行读取,则抛出FileNotFoundException; //注意:FileInputStream只能读取并显示纯文本文件的内容(也就是能用记事本打开的文件) fin = new FileInputStream("d:\\aa.txt"); int i = 0; //从文件中读取一个字节以int值返回,当读到文件末尾没有数据时返回-1; while((i=fin.read()) != -1){ //byte->int->char System.out.print((char)i); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //流打开之后,必须进行关闭 try { if(fin != null){ fin.close(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 用FileInputStream读取文件的内容,一次读取多个字节的内容(有些中文没有乱码,有些乱码;并且还多读了一些内容) */ public static void readFile2() { FileInputStream fin = null; try { fin = new FileInputStream("d:\\aa.txt"); int i = 0; byte[] b = new byte[100]; //从文件中读取100个字节,放大数组中,i返回的是读取到的字节的数量,当读到文件末尾没有数据时返回-1; while((i=fin.read(b)) != -1){ //byte[] -> char[] -> String String str = new String(b); System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //流打开之后,必须进行关闭 try { if(fin != null){ fin.close(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 用FileOutputStream写String到文件 */ public static void writeFile1(){ FileOutputStream fout = null; try { //当目标文件不存在时,JVM会自动创建这个文件,若已存在,则将内容写到这个文件中; fout = new FileOutputStream("d:\\bb.txt",true); //true表示追加写文件 String str = "2.用FileOutputStream写String到文件"; //将String转换为byte[] byte[] b = str.getBytes(); fout.write(b); System.out.println("文件写入完成"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //流打开之后,必须进行关闭 try { if(fout != null){ fout.close(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 用FileOutputStream写String到文件,解决换行 */ public static void writeFile2(){ FileOutputStream fout = null; try { //当目标文件不存在时,JVM会自动创建这个文件,若已存在,则将内容写到这个文件中; fout = new FileOutputStream("d:\\bb.txt",true); //true表示追加写文件 for(int i=0;i<=10;i++){ //由于FileOutputStream不会处理换行,需要我们在String结尾加入\n //记事本不能识别\n,能够识别\r\n; String str = i+".用FileOutputStream写String到文件\r\n"; //将String转换为byte[] byte[] b = str.getBytes(); fout.write(b); } System.out.println("文件写入完成"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //流打开之后,必须进行关闭 try { if(fout != null){ fout.close(); } } catch (IOException e) { e.printStackTrace(); } } }}
然后将FileInputStream和FileOutputStream拼接起来,就组成了拷贝粘贴的功能;
但是这样直接使用低级流去读和写文件显然不是我们想要的,因为效率有点低,于是我们需要在低级流上接上一个高级流,像这样:
下面有代码来实现上面的过程,也即是拷贝文件的过程:
package com.Jevin.io;import java.io.*;/** * 文件拷贝器 */public class FileCoper { private FileCoper(){ } /** * 拷贝文件的操作 * @param srcFile 源文件 * @param desPath 目标路径 */ public static void copyFile(String srcFile,String desPath){ //从源文件的String中取出文件名称,也就是从例如:"D:\\tools\\Navicat.rar"中取到"Navicat.rar"; String[] arr = srcFile.split("\\\\"); String fileName = arr[arr.length-1]; //组织目标文件:路径名称+分隔符+文件名称 String desFile = desPath+"\\"+fileName; //声明输入流: FileInputStream fin = null; BufferedInputStream bin = null; //声明输出流: FileOutputStream fout = null; //低级输出流 BufferedOutputStream bout = null; //高级输出流 try { //创建输入流: fin = new FileInputStream(srcFile); bin = new BufferedInputStream(fin); //创建输出流:首先初始化低级流,然后初始化高级流 fout = new FileOutputStream(desFile); bout = new BufferedOutputStream(fout); int i=0; byte[] b = new byte[1024*1024]; while((i=bin.read(b))!=-1){ bout.write(b,0,i); } /** * 如果关闭低级流,那么会导致小于8k的文件滞留在bout的缓冲区,所以要刷新缓存区 * 拷贝过程中,只要文件的大小大于8k,则无需刷新缓存 */ //bout.flush(); //由于采用了正确的流的关闭方式,所以无论文件大小,无需刷新缓存 System.out.println("文件拷贝完成,"+srcFile+"拷贝到目标文件"+desFile+"完成"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //错误的关闭方式 //if(fin != null){ // try { // fin.close(); // } catch (IOException e) { // e.printStackTrace(); // } //} //if(fout != null){ // try { // fout.close(); // } catch (IOException e) { // e.printStackTrace(); // } //} //正确的关闭方式 //高级流在关闭的时候:1.会自动关闭低级流;2.会自动刷新缓存 try { if(bin != null){ bin.close(); } } catch (IOException e) { e.printStackTrace(); } try { //bout关闭的时候,会自动刷新缓冲区 if(bout != null){ bout.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args){ String file = "D:\\tools\\aa.txt"; //String file = "D:\\tools\\Navicat.rar"; copyFile(file,"d:\\"); }}
但是上面的代码有点问题,问题出在取文件名和拼接目标文件路径上,采用了"\\"和"\\\\"这两种分隔符,但是不同操作系统上的分隔符是不一样的,这样写的话就写死了,违背java的可移植性特点,所以我们采用java.io.File来解决这个问题:
package com.Jevin.io;import java.io.*;/** * 文件拷贝器 */public class FileCoper { private FileCoper(){ } /** * 拷贝文件的操作 * @param srcFile 源文件 * @param desPath 目标路径 */ public static void copyFile(String srcFile,String desPath){ //从源文件的String中取出文件名称 //File file = new File(srcFile); //copyFile(file,desPath); //继续简化 copyFile(new File(srcFile),desPath); } /** * 拷贝文件的操作 * @param srcFile 源文件 * @param desPath 目标路径 */ public static void copyFile(File srcFile,String desPath){ //从源文件的String中取出文件名称 String fileName = srcFile.getName(); //组织目标文件:路径名称+分隔符+文件名称 String desFile = desPath+File.separator+fileName; //声明输入流: FileInputStream fin = null; BufferedInputStream bin = null; //声明输出流: FileOutputStream fout = null; //低级输出流 BufferedOutputStream bout = null; //高级输出流 try { //创建输入流: fin = new FileInputStream(srcFile); bin = new BufferedInputStream(fin); //创建输出流:首先初始化低级流,然后初始化高级流 fout = new FileOutputStream(desFile); bout = new BufferedOutputStream(fout); int i=0; byte[] b = new byte[1024*1024]; while((i=bin.read(b))!=-1){ bout.write(b,0,i); } System.out.println("文件拷贝完成,"+srcFile+"拷贝到目标文件"+desFile+"完成"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //正确的关闭方式 //高级流在关闭的时候:1.会自动关闭低级流;2.会自动刷新缓存 try { if(bin != null){ bin.close(); } } catch (IOException e) { e.printStackTrace(); } try { //bout关闭的时候,会自动刷新缓冲区 if(bout != null){ bout.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args){ String file = "D:\\tools\\aa.txt"; //String file = "D:\\tools\\Navicat.rar"; copyFile(file,"d:\\"); }}
但是这里还有一个问题,一个相对较大的文件拷贝是需要时间的,如果在拷贝的这段时间内,程序突然停下来的,文件看似拷贝下来了,但是这个文件是不完整的,那么该如何解决这个问题呢?下面代码演示:
package com.Jevin.io;import java.io.*;/** * 文件拷贝器 */public class FileCoper { private FileCoper(){ } /** * 拷贝文件的操作 * @param srcFile 源文件 * @param desPath 目标路径 */ public static void copyFile(String srcFile,String desPath){ //从源文件的String中取出文件名称 //File file = new File(srcFile); //copyFile(file,desPath); //继续简化 copyFile(new File(srcFile),desPath); } /** * 拷贝文件的操作 * @param srcFile 源文件 * @param desPath 目标路径 */ public static void copyFile(File srcFile,String desPath){ //从源文件的String中取出文件名称 String fileName = srcFile.getName(); //判断目标路径是否存在,如果不存在,就把他创造出来; File dpath = new File(desPath); if(!dpath.exists()){ dpath.mkdirs(); } //组织目标文件:路径名称+分隔符+文件名称 String desFile = desPath+File.separator+fileName; String tempFile = desFile + ".td"; //拷贝过程中的临时文件名称 //声明输入流: FileInputStream fin = null; BufferedInputStream bin = null; //声明输出流: FileOutputStream fout = null; //低级输出流 BufferedOutputStream bout = null; //高级输出流 try { //创建输入流: fin = new FileInputStream(srcFile); bin = new BufferedInputStream(fin); //创建输出流:首先初始化低级流,然后初始化高级流 fout = new FileOutputStream(tempFile); bout = new BufferedOutputStream(fout); int i=0; byte[] b = new byte[1024*1024]; while((i=bin.read(b))!=-1){ bout.write(b,0,i); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //正确的关闭方式 //高级流在关闭的时候:1.会自动关闭低级流;2.会自动刷新缓存 try { if(bin != null){ bin.close(); } } catch (IOException e) { e.printStackTrace(); } try { //bout关闭的时候,会自动刷新缓冲区 if(bout != null){ bout.close(); } } catch (IOException e) { e.printStackTrace(); } //当文件拷贝完成之后,把临时文件名称改成目标文件名称 //这里注意:需要在流关闭的时候才可以重命名,要不然流将线程锁住,无法改名 File file1 = new File(desFile); File file2 = new File(tempFile); if(file2.renameTo(file1)){ System.out.println("文件拷贝完成,"+srcFile+"拷贝到目标文件"+desFile+"完成"); }else{ System.err.println("拷贝失败"); } } } public static void main(String[] args){ //String file = "D:\\tools\\aa.txt"; String file = "D:\\tools\\Navicat.rar"; copyFile(file,"d:\\abc"); }}
但是这里还有一个问题,以上只是解决了拷贝一个文件到任意指定的文件夹中,但是实际还有将任意文件夹(包括其中的文件)拷贝到任意文件夹中,这个又该如何实现呢?一下代码演示:
package com.Jevin.io;import java.io.*;/** * 文件拷贝器 */public class FileCoper { private FileCoper(){ } /** * 拷贝文件的操作 * @param srcFile 源文件 * @param desPath 目标路径 */ public static void copyFile(File srcFile,String desPath){ //从源文件的String中取出文件名称 String fileName = srcFile.getName(); //判断目标路径是否存在,如果不存在,就把他创造出来; File dpath = new File(desPath); if(!dpath.exists()){ dpath.mkdirs(); } //组织目标文件:路径名称+分隔符+文件名称 String desFile = desPath+File.separator+fileName; String tempFile = desFile + ".td"; //拷贝过程中的临时文件名称 //声明输入流: FileInputStream fin = null; BufferedInputStream bin = null; //声明输出流: FileOutputStream fout = null; //低级输出流 BufferedOutputStream bout = null; //高级输出流 try { //创建输入流: fin = new FileInputStream(srcFile); bin = new BufferedInputStream(fin); //创建输出流:首先初始化低级流,然后初始化高级流 fout = new FileOutputStream(tempFile); bout = new BufferedOutputStream(fout); int i=0; byte[] b = new byte[1024*1024]; while((i=bin.read(b))!=-1){ bout.write(b,0,i); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //正确的关闭方式 //高级流在关闭的时候:1.会自动关闭低级流;2.会自动刷新缓存 try { if(bin != null){ bin.close(); } } catch (IOException e) { e.printStackTrace(); } try { //bout关闭的时候,会自动刷新缓冲区 if(bout != null){ bout.close(); } } catch (IOException e) { e.printStackTrace(); } //当文件拷贝完成之后,把临时文件名称改成目标文件名称 //这里注意:需要在流关闭的时候才可以重命名,要不然流将线程锁住,无法改名 File file1 = new File(desFile); File file2 = new File(tempFile); if(file2.renameTo(file1)){ System.out.println("文件拷贝完成,"+srcFile+"拷贝到目标文件"+desFile+"完成"); }else{ System.err.println("拷贝失败"); } } } /** * 拷贝文件夹的功能 * @param srcPath 原文件夹 * @param desPath 目标路径 */ public static void copyDir(String srcPath,String desPath){ copyDir(new File(srcPath),desPath); } /** * 拷贝文件夹的功能 * @param srcPath 原文件夹 * @param desPath 目标路径 */ public static void copyDir(File srcPath,String desPath){ //判断srcPath是文件还是文件夹: if(srcPath.isFile()){ //是文件,那么调用之前拷贝文件的方法: copyFile(srcPath,desPath); }else{ //是文件夹: //从原文件中获取要拷贝的文件夹名称,例如,我要拷贝jdk源代码"D:\\file\\src",从中获取src String pathName = srcPath.getName(); //在目标路径中创建要拷贝的目录,例如在"d:\\"创建"src"目录: String dPath = desPath+File.separator+pathName; //"d:\\src" File file = new File(dPath); file.mkdirs(); //获取原文件夹中所有子文件(夹) File[] files = srcPath.listFiles(); //遍历存放子文件的数组,一个个的拷贝到目标路径中,也就是dPath for(File file1:files){ copyDir(file1,dPath); //递归调用 } } } public static void main(String[] args){ //String file = "D:\\tools\\aa.txt"; //String file = "D:\\tools\\Navicat.rar"; //copyFile(file,"d:\\abc"); String file = "D:\\file\\src"; copyDir(file,"d:\\"); }}
这里文件或文件夹拷贝告一段落了,反观之前的FileOutputStream这个低级流在实现写的时候,有一些瑕疵,模型如下:
package com.Jevin.io;import java.io.*;public class ByteStreamTest { public static void main(String[] args) { writeFile3(); } /** * 在FileOutputStream上接一个高级流PrintStream输出 */ public static void writeFile3(){ FileOutputStream fout = null; PrintStream ps = null; try { fout = new FileOutputStream("d:\\dd.txt",true); ps = new PrintStream(fout); for(int i=0;i<1000;i++){ String str = i+"在FileOutputStream上接一个高级流PrintStream输出"; //为了方便操作,ps.println(str)/ps.close()暂时不需要处理异常; ps.println(str); } } catch (FileNotFoundException e) { e.printStackTrace(); }finally{ if(ps != null){ ps.close(); } } }}
================================================================================================
以上讲的都是字节流,从这里我们开始讲讲字符流的使用。
首先是使用字符流去读取文件中的数据,模型如下:
以下是代码演示:
package com.Jevin.io;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.IOException;public class CharacterStreamTest { public static void main(String[] args){ //readFile1(); readFile2(); } /** * 用FileReader读取文件的内容,一个一个字符的读取 */ public static void readFile1(){ FileReader fr = null; try { fr = new FileReader("d:\\aa.txt"); int i = 0; //读取一个字符,当做int返回,直到文件末尾没有字符时返回-1; while((i=fr.read()) != -1){ //char -> int -> char System.out.print((char)i); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ try { if(fr != null){ fr.close(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 用FileReader读取文件的内容,一次读取多个字符的数据 */ public static void readFile2(){ FileReader fr = null; try { fr = new FileReader("d:\\aa.txt"); int i = 0; char[] c = new char[1024]; //读取多个字符,保存到数组中,i是读取到的字符的数量 while((i=fr.read(c)) != -1){ //char[] -> String String str = new String(c,0,i); System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ try { if(fr != null){ fr.close(); } } catch (IOException e) { e.printStackTrace(); } } }}
但是这样用低级流去读取文件是字符数组,然后还要手动转换为字符串,这样在读取大文件时效率不高,这时我没需要在低级流上接一个高级流,利用高级流的缓存一行一行的读取数据,效率大大提高,一下模型演示:
下面是代码演示:
package com.Jevin.io;import java.io.BufferedReader;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.IOException;public class CharacterStreamTest { public static void main(String[] args) { readFile3(); } /** * 用FileReader和BufferReader读取文件中的内容 */ public static void readFile3() { FileReader fr = null; BufferedReader br = null; try { fr = new FileReader("d:\\aa.txt"); br = new BufferedReader(fr); String str = null; while ((str = br.readLine()) != null) { System.out.println(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }}
这里简单的字节流和字符流就介绍到这里,接下来介绍一个有意思的流:RandomAccessFile,它可以从文件的任意位置读,也可以从文件的任意位置写。写个功能有什么用呢?换言之:可以将一个大文件切割成多个小文件分别读写。
模型如下所示:
代码如下所示:
package com.Jevin.io.demo;import java.io.File;/** * 文件拷贝包工头 */public class FileCopyContractor { private File srcFile; //源文件 private String desFile; //目标文件 private String tempFile; //拷贝过程中的临时文件 private int splitCount; //文件切分的份数 public FileCopyContractor(){} public FileCopyContractor(File srcFile, String desPath, int splitCount) { super(); this.srcFile = srcFile; this.splitCount = splitCount; //组织目标文件: String fileName = srcFile.getName(); this.desFile = desPath + File.separator + fileName; this.tempFile = desFile + ".td"; } public FileCopyContractor(String srcFile, String desPath, int splitCount) { this(new File(srcFile),desPath,splitCount); } /** * 包工头开始工作: */ public void start(){ //获取原文件的大小: long fileSize = srcFile.length(); System.out.println("文件的大小是:"+fileSize); //根据切分的份数和源文件的大小,计算每个工人的平均工作量 long perWorkerSize = fileSize/this.splitCount; //计算第一个工人的开始位置和结束位置: long startPost = 0L; long endPost = perWorkerSize; //包工头创建多个工人: for(int i=0;i
package com.Jevin.io.demo;import java.io.File;import java.io.FileNotFoundException;import java.io.IOException;import java.io.RandomAccessFile;/** * 文件拷贝工人 */public class FileCopyWorker { private String name; //工人名称 private File srcFile; //源文件 private String desFile; //目标文件 private long startPost; //开始位置 private long endPost; //结束位置 private long copyedPost; //已经拷贝的位置 public FileCopyWorker(String name, File srcFile, String desFile, long startPost, long endPost) { super(); this.name = name; this.srcFile = srcFile; this.desFile = desFile; this.startPost = startPost; this.endPost = endPost; this.copyedPost = this.startPost; //初始化拷贝位置即为初始位置 System.out.println(name+"[开始位置是:"+this.startPost+",结束位置是:"+this.endPost+"]"); } /** * 工人开始工作 */ public void work(){ RandomAccessFile rin = null; //读数据流 RandomAccessFile rout = null; //写数据流 try { rin = new RandomAccessFile(this.srcFile,"r"); rout = new RandomAccessFile(this.desFile,"rw"); //定位读写的位置: rin.seek(this.startPost); //开始读的位置 rout.seek(this.startPost); //开始写的位置 byte[] b = new byte[1024*1024]; int i = 0; //当已经拷贝的位置小于结束位置,并且未拷贝到文件结尾,就一直循环拷贝下去: while((this.copyedPost < this.endPost) && (i=rin.read(b)) != -1){ if((this.copyedPost + i) > this.endPost){ i = (int) (this.endPost - this.copyedPost); } rout.write(b,0,i); this.copyedPost += i; System.out.println(name+"正在工作,已经拷贝的位置是:"+this.copyedPost+",结束位置是:"+this.endPost); } System.out.println(name+"结束工作,已经拷贝的位置是:"+this.copyedPost+",结束位置是:"+this.endPost); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ try { if(rin != null){ rin.close(); } if(rout != null){ rout.close(); } } catch (IOException e) { e.printStackTrace(); } } }}
package com.Jevin.io.demo;public class MainTest { public static void main(String[] args){ String file = "D:\\tools\\Navicat.rar"; FileCopyContractor fileCopyContractor = new FileCopyContractor(file,"d:\\",15); fileCopyContractor.start(); }}
“流”暂时就到这里吧,又补充的再继续吧!
转载地址:http://trtvb.baihongyu.com/