IO操作一: 文件操作、字节流与字符流 - Java高级特性 10

目录

文件操作

File类的使用

获取文件信息

案例分析

案例一:列出指定目录的全部文件

案例二:批量修改文件 名称

字节流与字符流

OutputStream字节输出流

InputStream字节输入流

Writer字符输出流

Reader字符输入流

字符与字节流的区别

转换流

案例分析:文件拷贝


注:掌握文件夹拷贝,就能掌握核心

文件操作

在Java语言里面提供有对于文件操作系统的支持,而这个支持就在java.io.File类中进行了定义,也就是说在java.io.File包里面。File类是唯一一个与文件本身操作(创建、删除、重命名等)有关的类,而如果要进行File类的操作,必须要提供有完整的路径才可以调用相应的方法进行处理。

File类的使用

打开JDK文档可以发现,File类是Comparable接口的子类,所以来讲File类的对象可以进行排序处理的。而在进行File类处理的时候需要为其设置访问路径,那么对于路径的配置主要通过File类的构造方法进行处理;

  • 构造方法:public File​(String pathname),pathname:操作的完整路径 ;

  • 构造方法:public File​(File parent, String child); 设置父路径与子目录;

如果现在要想进行文件的基本操作,可以使用如下的方法:

  • 创建新的文件:public boolean createNewFile​() throws IOException

  • 判断文件是否存在:public boolean exists​()

  • 删除文件:public boolean delete​()

范例:使用File类创建文件(d:\ren.txt)

package cn.ren.demo;
import java.io.File;
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception  {
		File file = new File("d:\\ren.txt") ;
		if (file.exists()) {
			file.delete() ; // 删除文件 
		} else {
			System.out.println(file.createNewFile()) ;  // 创建新的文件
		}
	}	
}

通过代码可以发现,File类实现的就是文件本身的处理。

现在已经实现了文件的基础操作,但是对于操作里面也是存在一些问题的,下面针对于这前的代码进行优化处理。

1、在实际的软件项目开发和运行的过程之中,往往都会在Windows系统中进行项目的开发,而在项目部署的时候基于Linux或Unix系统来进行项目的发布,以保证生产环节的安全性:

在不同的操作系统之中会存在不同的路径分割符:Windows分隔符“\”、Linux分割符“/”,所以在最初进行开发的时候就必须考虑不同系统环境下的分割符的问题,所以为了解决此问题,File类提供有一个常量:public static final String separator

范例:正常的路径编写

File file = new File("d:" + File.separator+ "ren.txt") ;

但是随着系统的适应性不断的加强,对于当前的路径操作也可以随意使用了。

File file = new File("d:/ren.txt") ;

2、在使用File类进行文件处理时,需要注意:程序 -> JVM -> 操作系统函数 ->  文件处理,所以在进行重复的文件删除或创建的时候有可能会出现有延迟的问题,所以这个时候最好的方案是别重名;

3、在进行文件创建的时候有一个重要的前提:文件的父路径必须首先存在。

  • 获取父路径:public File getParentFile​()
  • 创建目录(路径):public boolean mkdirs​()
package cn.ren.demo;
import java.io.File;
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception  {
		File file = new File("d:" + File.separator + "first" + 
									File.separator + "Second" +File.separator +   "ren.txt") ;
		if ( !file.getParentFile().exists()) {  // 父路径不存在
			file.getParentFile().mkdirs() ; // 创建父路径
		}
		if (file.exists()) {
			file.delete() ; // 删除文件 
		} else {
			System.out.println(file.createNewFile()) ;  // 创建新的文件
		}
	}	
}

这种判断并且建立父目录的操作在很多情况下可能只需要一次,都是如果将这个判断一直都停留在代码里面,那么就会造成时间复杂度的提升,所以这个时候如果要想提升性能,请先保证目录已经创建。

获取文件信息

除了可以进行文件的操作之外也可以通过File类来获取一些文件本身提供的一些信息:

  • 文件是否可读:public boolean canRead​()

  • 文件是否可写:public boolean canWrite​()

  • 获取文件长度:public long length​(),该方法返回的是Long数据类型,字节长度 ;

  • 最后一次修改日期时间:public long lastModified​(); 

  • 判断是否是目录:public boolean isDirectory​()

  • 判断是否是文件:public boolean isFile​()

package cn.ren.demo;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
class MathUtil {
	public static double round(double num, int scale) {
		return Math.round(Math.pow(10, scale)) / Math.pow(10, scale) ;
	}
}
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception  {
		File file = new File("d:" + File.separator + "my.jpg" ) ;
		System.out.println("文件是否可读:" + file.canRead());
		System.out.println("文件是否可写:" + file.canWrite());
		System.out.println("文件大小:" + MathUtil.round(file.length() / (double)1024 / 1024, 2));
		System.out.println("最后的修改日期时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(file.lastModified())));
		System.out.println("是否是目录:" + file.isDirectory());
		System.out.println("是否是文件:" + file.isFile());
	}	
}

 

既然可以判断给定的路径是文件还是目录,则可以进一步的思考,如果是目录,则应该列出目录中的全部内容

  • 列出目录内容:public File[] listFiles​()

package cn.ren.demo;
import java.io.File;

class MathUtil {
	public static double round(double num, int scale) {
		return Math.round(Math.pow(10, scale)) / Math.pow(10, scale) ;
	}
}
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception  {
		File file = new File("d:" + File.separator) ;
		if (file.isDirectory()) { 
			File result [] = file.listFiles() ; // 列出目录中的数组
			for( int x = 0; x < result.length; x ++) {
				System.out.println(result[x]);
			}
		}
	}	
}

这些信息的获取都是文件或目录本身的操作,都是不涉到文件内容处理的。

案例分析

案例一:列出指定目录的全部文件

现在由开发任意设置一个目录路径,而后将这个目录中所有的文件的信息全部列出,包括子目录的全部文件,在这样的处理情况下最好的做法使用递归完成。

package cn.ren.demo;
import java.io.File;
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception  {
		File file = new File("D:" + File.separator + "renjava" + File.separator + "ren" + File.separator + "cn" + File.separator + "ren") ;
		JavaAPIDemo.listDir(file);
	}	
	public static void listDir(File file) {
		if (file.isDirectory()) {  // 是一个目录
			File nowDirectory [] = file.listFiles() ; // 找到目录内容
			if (nowDirectory != null) { // 防止出现拒绝访问文件
				for (int x = 0; x < nowDirectory.length; x++) {
					System.out.println("\t|-" + nowDirectory[x]); // 列出
					if (nowDirectory[x].isDirectory()) { //递归
						JavaAPIDemo.listDir(nowDirectory[x]);
					}
				}
			} else {
				return; // 递归出口
			}
		}
	}
}

 

如果将路径输出变为删除,那么就彻底删除路径了。

	package cn.ren.demo;
	import java.io.File;
	public class JavaAPIDemo {
		public static void main(String[] args) throws Exception  {
			File file = new File("D:" + File.separator + "renjava" + File.separator + "ren" + File.separator + "cn" + File.separator + "ren") ;
			JavaAPIDemo.listDir(file);
		}	
		public static void listDir(File file) {
			if (file.isDirectory()) {  // 是一个目录
				File nowDirectory [] = file.listFiles() ; // 找到目录内容
				if (nowDirectory != null) { // 防止出现拒绝访问文件
					for (int x = 0; x < nowDirectory.length; x++) {
						if (nowDirectory[x].isDirectory()) { //递归
							JavaAPIDemo.listDir(nowDirectory[x]);
						}
					}
				} else {
					return; // 递归出口
				}
			}
			file.delete() ;
		}
	}
	

上述代码可以写个定时操作,客户没结账的时候,自动删除。

案例二:批量修改文件 名称

输入目录名称,并把该目录下的所有后缀名修改为.txt。

对于这类需要考虑到:能够重名名的文件都是有后缀的,如果没有则追加路径;如果有后缀的路径,则必须以最后一个“.”进行截取。

	package cn.ren.demo;
	import java.io.File;
	public class JavaAPIDemo {
		public static void main(String[] args) throws Exception  {
			File file = new File("D:" + File.separator + "renjava" ) ;
			long start = System.currentTimeMillis() ;
			JavaAPIDemo.renameDir(file);
			long end = System.currentTimeMillis() ;
			System.out.println("本次操作所花费的时间:" + (end-start));
		}	
		public static void renameDir(File file) {
			if (file.isDirectory()) {  // 是一个目录
				File  result [] = file.listFiles() ;
				if (result != null) {
					for(int x = 0; x < result.length; x ++) {
						renameDir(result[x]) ;
					} 
				}
			}else {
				if (file.isFile()) {   // 如果是文件,则需要重命名
					// 找出原始文件名,并得到新的重名的文件名
					String fileName = null ; 
					if (file.getName().contains(".")) {  // 文件名包含点
						fileName = file.getName().substring(0, file.getName().lastIndexOf(".")) + ".txt" ;
					} else {
						fileName = file.getName() + ".txt" ;
					}
					// ---- 下面开始重命名 ---
					File newFile = new File(file.getParent(), fileName) ; // 新的文件名称
					file.renameTo(newFile) ; // 重命名
				}		
			}
		}
	}
	
	 

在面试的过程之中,经常出现给你一个路径,然后进行名称或者文件的批量修改操作,那么就采用以上的带啊吗结构即可

字节流与字符流

在java.io包里面File类是唯一一个与文件本身有关的程序类,但是File类只能够操作文件本身而不能操作文件内容,或者说在实际开发中IO操作的核心意义在于:输入与输出操作。而对于程序而言输入与输出可能来自于不同的环境,例如:通过电脑连接到服务器上进行浏览的时候,实际上此时客户端发出了一个信息,而后服务器接收到此信息后进行回应处理。

对于服务器或者客户端而言实质上,传递的是一种数据流的处理形式,而所谓的数据流指的就是字节数据。而对于这种流的处理形式,在java.io包里面提供有两类支持:

  • 字节处理流:OutputStream(输出字节流)、ImputStream(输入字节流);

  • 字符处理流:Writer(输出字符流)、Reader(输入字符流);

所有的流操作都应该采用如下统一的步骤进行,下面以文件处理的流程为例:

  • 如果现在要进行的是文件的读写操作,则一定要通过File类找到一个文件路径;

  • 通过字节流或字符流的子类为父类对象实例化;

  • 利用字节流或字符流的方法实现程序的输入与输出操作;

  • 流的操作属于资源操作,资源操作必须进行关闭处理;

OutputStream字节输出流

字节的数据是以byte类型为主实现的操作,在进行字节内容输出的时候可以使用OutputStream类完成,这个类的基本定义如下:public abstract class OutputStream extends Object implements Closeable, Flushable

首先可以发现这个类实现了两个接口,于是基本的对应关系如下:

 

CloseableFlushable
public interface Closeable    extends AutoCloseable {
    void close() throws IOException ;    
}
public interface Flushable{
    void flush​() throws IOException;
}

注:继承关系能背最好

OutputStream类定义的是一个公共的输出操作标准,而在这个操作标准里面,定义有三个内容输出的方法:

No.方法名称类型描述
01public abstract void write​(int b)  throws IOException;普通输出单个字节数据
02public void write​(byte[] b) throws IOException;普通输出一组字节数据
03public void write​(byte[] b,  int off, int len) throws  IOException ;普通输出部分字节数据

但是需要注意的是OutputStream类毕竟是一个抽象类,而这个抽象类如果要想获得实例化对象,按照传统的认识应该通过子类实例的向上转型,如果说现在要进行的是文件处理操作,则可以使用FileOutputStream子类;

因为最终都需要发生向上转型的处理关系,所以对于此时的FileOutputStream子类核心的关注点就可以放在构造方法:

  • 【覆盖】构造方法:public FileOutputStream​(File file) throws FileNotFoundException ;

  • 【追加】构造方法:public FileOutputStream​(File file, boolean append) throws FileNotFoundException ;

范例:使用OutputStream类实现内容的输出

package cn.ren.demo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("D:" + File.separator + "ren57"
				+ File.separator + "test.txt");		 // 1、指定要操作文件目录
		if (!(file.getParentFile().exists())) { 	 // 文件不存在
			file.getParentFile().mkdirs() ; 		 // 创建文件父目录
		}
		OutputStream output = new FileOutputStream(file) ; // 2、 通过子类实例化
		String str = "ren57" ; //  要输出的文件内容
		output.write(str.getBytes()) ;// 3、 将字符串变为字节数组并输出
		output.close(); // 4、关闭资源
	}
}

本程序是采用最为标准的形式实现了输出的操作处理,并且在整体的操作处理之中,只是创建了文件的父目录,但是并没有创建文件而在执行后,发现文件可以帮助用户自动创建。需要注意的是,由于OutputStream的子类也属于AutoCloseable接口的子类,所以对于close()方法也可以简化使用。

范例:自动关闭处理

package cn.ren.demo;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("D:" + File.separator + "ren57"
				+ File.separator + "test.txt");		 // 1、指定要操作文件目录
		if (!(file.getParentFile().exists())) { 	 // 文件不存在
			file.getParentFile().mkdirs() ; 		 // 创建文件父目录
		}
		try (OutputStream output = new FileOutputStream(file, true)){ // true 表示追加
			String str = "ren57\r\n" ; //  要输出的文件内容 ,\r\n表示标准换行
			output.write(str.getBytes()) ;// 3、 将字符串变为字节数组并输出
			output.close(); // 4、关闭资源
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

是否使用自动的关闭取决于项目的整体结构,还需要注意整个程序里面最终是输出了一组字节数据,但是不要忘记:OutputStream之中定义的输出方法一共有三个。

InputStream字节输入流

与OutputStream类对应的一个流就是字节输入流,InputStream类主要实现的就是字节数据的读取,该类的定义如下:public abstract class InputStream extends Object implements Closeable

在InputStreaml类中定义以下的几个核心方法:

No.方法名称类型描述
01public abstract int read​() throws IOException普通读取单个字节数据,如果现在已经读取到底,返回-1
02public int read​(byte[] b) throws IOException普通读取一组字节数据,返回的是读取的个数,如果有返回字节的个数,如果读取到底返回的是-1
03public int read​(byte[] b, int off, int len) throws IOException普通读取一组数据的部分数据

InputStream类是一个抽象类,此时应该靠它的子类来实例化对象,如果要从文件读取一定要使用FileInputStream子类;对于子类而言,我们只关心对父类对象实例化,构造方法:public abstract int read​() throws IOException

范例:读取数据

package cn.ren.demo;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("D:" + File.separator + "ren57"
				+ File.separator + "test.txt");		 // 1、指定要操作文件目录
		InputStream input = new FileInputStream(file) ;
		byte data [] = new byte [1024] ; // 开辟一个缓冲区读取数据,要大于读取的长度
		int len = input.read(data) ;  // 读取数据,数据全部保存在字节数组data之中,返回读取个数
		System.out.println("【" + new String(data, 0, len) + "】") ;
		input.close() ;
	}
}

对于字节输入流里面最为麻烦的在于:使用read()方法读取的时候只能够以字节数组为主进行接收。

需要注意从JDK1.9开始,在InputStream类中增加了一个新的方法:public byte[] readAllBytes​() throws IOException

范例:新方法

package cn.ren.demo;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("D:" + File.separator + "ren57"
				+ File.separator + "test.txt");		 // 1、指定要操作文件目录
		InputStream input = new FileInputStream(file) ;
		byte data [] =  input.readAllBytes() ;  // 读取全部数据
		System.out.println("【" + new String(data) + "】") ;
		input.close() ;
	}
}

如果现在要读取的内容很大很大(大于10K)的时候,那么这种读取会直接搞死你的程序。

Writer字符输出流

使用OutputStream字节进行数据输出的时候使用的都是字节类型的数据,而在很多情况下字符串的输出是比较方便的,所以对于java.io包而言,在JDK1.1的时候又推出了字符输出流:Writer,这个类的定义如下:

public abstract class Writer extends Object implements Appendable, Closeable, Flushable

在Writer类里面提供有许多的输出方法,重点来看两个:

  • 输出字符数组:public void write​(char[] cbuf) throws IOException

  • 输出字符串:public void write​(String str) throws IOException

在OutputStream中输出,需要将字符转换为byte类型输出,而这里可以输出字符串,这是与OutputStream最大的区别。

范例:使用Writer输出

package cn.ren.demo;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("D:" + File.separator + "ren57"
				+ File.separator + "test.txt");		 // 1、指定要操作文件目录
		if (!(file.getParentFile().exists())) {		// 父目录不存在
			file.getParentFile().mkdirs() ; 		// 创建父目录
		}
		Writer out = new FileWriter(file, true) ; // true表示追加
		String str = "helloded!\r\n" ;
		out.write(str);
		out.append("world,中国") ; // 追加输出内容
		out.close();
	}
}

使用Writer输出的最大优势在于可以利用字符串完成。Writer是字符流,字符处理的优势在于中文数据上。

Reader字符输入流

Reader是实现字符输入流的一种类型,其本身属于一个抽象类,这个类的定义如下:

public abstract class Reader extends Object implements Readable, Closeable

Reader类中并没有像Writer类一样并没有提供整个的(输入)操作,只能利用字符数组进行接收:

  • 接收数据:public int read​(char[] cbuf) throws IOException

范例:实现数据读取 

package cn.ren.demo;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("D:" + File.separator + "ren57"
				+ File.separator + "test.txt");		 // 1、指定要操作文件目录
		if (file.exists()) { // 文件存在
			Reader in = new FileReader(file) ;
			char data [] = new char[1024] ;
			int len = in.read(data) ;
			System.out.println("读取内容:\n" + new String(data,0,len));
			in.close(); 
		}
	}
}

字符流读取的时候只能够按照数组的形式进行操作。

字符与字节流的区别

现在通过一系列的分析,已经清楚字符流与字节流的基本操作了,但是这两种流依然是有区别的,重点来分析输出的处理操作。在使用OutputStream和writer输出最后都发现使用了close()方法进行了关闭处理。

在使用OutputStream类输出的时候,如果现在没有使用close()的方法关闭输出流发现,内容依然可以实现正常的输出。但是使用Writer类的时候没有使用close()方法关闭输出流,那么这个时候内容将无法进行输出,因为Writer使用到了缓冲区,当使用close()方法的时候实际上会出现有强制刷新缓冲区的情况,所以这个时候会将内容进行输出,如果没有关闭将无法进行输出操作,所以此时在不关闭的情况下要想将全部内容输出可以使用flush()方法强制清空。

范例:使用Writer并强制性清空

package cn.ren.demo;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("D:" + File.separator + "ren57"
				+ File.separator + "test.txt");		 // 1、指定要操作文件目录
		if (!(file.getParentFile().exists())) {		// 父目录不存在
			file.getParentFile().mkdirs() ; 		// 创建父目录
		}
		Writer out = new FileWriter(file, true) ; // true表示追加
		String str = "helloded!\r\n" ;
		out.write(str);
		out.append("world,中国") ; // 追加输出内容
		out.flush();  // 强制性刷新
	}
}

字节流在进行处理的时候并没有使用到缓冲区,但是字符流在处理的时候使用到了缓冲区。另外使用缓冲区的字符流更加适合于进行中文数据的处理,所以在日后的程序开发中,如果涉及到中文信息的输出一般都会使用字符流处理。但是从另外一方面讲,字节流和字符流的基本处理形式是相似的,有与IO很多情况下都是进行数据的传输使用(二进制)所以本次将以字节流为主。

转换流

所谓转换流指的是可以将字节流和字符流操作转换,例如:进行输出的时候OutputStream需要将内容变为字节数组后才可以输出,而Writer可以直接输出字符串,这点是方便的,所以很多人就需要提供一种转换机制,来进行不同流类型的转换操作,为此在java.io包里面提供有两个类:InputStreamReader、OutputStreamWriter。

InputStreamReader

OutputStreamWriter
定义public class InputStreamReader extends Readerpublic class OutputStreamWriter extends Writer
构造方法InputStreamReader​(InputStream in)OutputStreamWriter​(OutputStream out)

通过类的继承结构和构造方法可以发现,所谓的转换处理就是将接收到的字节流对象通过向上转型变为字符流对象。

范例:观察转换

 

package cn.ren.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("D:" + File.separator + "ren57"
				+ File.separator + "test.txt");		 // 1、指定要操作文件目录
		if (!(file.getParentFile().exists())) {		// 父目录不存在
			file.getParentFile().mkdirs() ; 		// 创建父目录
		}
		OutputStream output = new FileOutputStream(file) ;
		Writer out = new OutputStreamWriter(output) ; // 字节流变为字符流
		out.write("hello 57"); // 直接字符串,字符流适合处理中文
		out.close();
	}
}

分析转换流的主要目的知道有这种功能,更多的是需要进行结构的分析处理。通过之前的字节流与字符流的一系列的分析之后,会发现OutputStream类有FileOutputStream直接子类、InputStream类有FileInoutStream直接子类,但是来观察一下FileWriter、FileReader类的继承关系。

FileWriterFileReader
public class FileWriter extends OutputStreamWriterpublic class FileReader extends InputStreamReader

实际上缓存都是指的是程序中间的一道处理缓冲区。下面来看数据传输流:

 

案例分析:文件拷贝

在操作系统里面有一个copy命令,这个命令的主要功能实现文件的拷贝处理,现在要求模拟这个命令通过初始化参数输入拷贝的源文件路径与拷贝的目标路径实现文件的拷贝处理。

需求分析:

  • 需要实现文件的拷贝操作,那么这种拷贝就有可能拷贝各种类型的文件,所以这个时候选用字符流;
  • 在进行拷贝的时候需要考虑到大文件的拷贝问题。

实现方案:

  • 方案一:使用InputStream类将全部要拷贝的内容直接读取到程序里面,而后一次性的输出到目标文件

          |- 如果拷贝的文件很大,基本上程序就死了。

  • 方案二:采用部分拷贝,读取一部分输出一部分,现在采用第二种方案。

          |- InputStream : public int read​(byte[] b) throws IOException

          |- OutputStream : public void write​(byte[] b,  int off, int len) throws  IOException

范例:实现文件拷贝处理

package cn.ren.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		if (args.length != 2) {
			System.out.println("命令执行错误,执行结构:java JavaAPIDemo 拷贝的源文件路径  目标文件路径");
			System.exit(1);
		}
		long start = System.currentTimeMillis() ;
		FileUtil fu = new FileUtil(args[0], args[1]) ;
		System.out.println(fu.copy() ? "文件拷贝成功" : "文件拷贝失败");
		long end = System.currentTimeMillis() ;		
		System.out.println("拷贝花费时间:" + (end - start));
	}
}

class FileUtil { // 文件操作类
	private File srcFile ; 		// 源文件路径
	private File desFile ; 		// 目标文件路径
	public FileUtil(File srcFile, File desFile) { // 构造方法一
		this.srcFile = srcFile;
		this.desFile = desFile;
	}
	public FileUtil(String src, String des) {    // 构造方法二
		this(new File(src), new File(des)) ;
	}	
	public boolean copy() throws Exception {   	// 文件的拷贝处理
		if (!this.srcFile.exists()) {   	// 源文件存在
			System.out.println("源文件不存在");
			return false ;
		}
		if (!this.desFile.getParentFile().exists()) {	// 目标文件父目录不存在
			this.desFile.getParentFile().mkdirs() ;  	// 创建父目录	
		}
		byte data [] = new byte [1024] ; 		//  开辟一个拷贝的缓冲区
		InputStream input =  null ;
		OutputStream output = null ;
		try {
			input = new FileInputStream(this.srcFile) ;
			output = new FileOutputStream(this.desFile) ;
			int len = 0 ;
			// 1、读取数据到数组之中,随后返回读取的个数  len = input.read(data)
			// 2、 判断个数是否是-1, 如果不是进行写入 len = input.read(data)) != -1
			while ((len = input.read(data)) != -1) {
				output.write(data); // 输出 ,没有关闭的时候记录读取位置,下一次从当前位置开始
			}
			return true ;
		} catch (Exception e) {
			throw e ;
		} finally {
			if (input != null) {
				input.close() ;
			}
			if (output != null) {
				output.close();
			}			
		}
		
 	}
	
}

注意代码中While的操作技巧。

while ((len = input.read(data)) != -1) {
	output.write(data); // 输出 ,没有关闭的时候记录读取位置,下一次从当前位置开始
}

但是需要注意的是,以上的做法是属于文件拷贝的最原始的实现,而从JDK1.9开始InputStream类和Reader类中都追加有数据转存的处理方法:

  • InputStream :  public long transferTo​(OutputStream out) throws IOException

  • Reader :  public long transferTo​(Writer out) throws IOException (现在文档中已经不在了)

范例:使用转存的方式处理

package cn.ren.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		if (args.length != 2) {
			System.out.println("命令执行错误,执行结构:java JavaAPIDemo 拷贝的源文件路径  目标文件路径");
			System.exit(1);
		}
		long start = System.currentTimeMillis() ;
		FileUtil fu = new FileUtil(args[0], args[1]) ;
		System.out.println(fu.copy() ? "文件拷贝成功" : "文件拷贝失败");
		long end = System.currentTimeMillis() ;		
		System.out.println("拷贝花费时间:" + (end - start));
	}
}

class FileUtil { // 文件操作类
	private File srcFile ; 		// 源文件路径
	private File desFile ; 		// 目标文件路径
	public FileUtil(File srcFile, File desFile) { // 构造方法一
		this.srcFile = srcFile;
		this.desFile = desFile;
	}
	public FileUtil(String src, String des) {    // 构造方法二
		this(new File(src), new File(des)) ;
	}	
	public boolean copy() throws Exception {   	// 文件的拷贝处理
		if (!this.srcFile.exists()) {   	// 源文件存在
			System.out.println("源文件不存在");
			return false ;
		}
		if (!this.desFile.getParentFile().exists()) {	// 目标文件父目录不存在
			this.desFile.getParentFile().mkdirs() ;  	// 创建父目录	
		}
		InputStream input =  null ;
		OutputStream output = null ;
		try {
			input = new FileInputStream(this.srcFile) ;
			output = new FileOutputStream(this.desFile) ;
			input.transferTo(output) ; // 转存
			return true ;
		} catch (Exception e) {
			throw e ;
		} finally {
			if (input != null) {
				input.close() ;
			}
			if (output != null) {
				output.close();
			}			
		}
		
 	}
	
}

处理时间3->1,此时要注意程序的运行版本。那么如果现在要求对此程序进一步的扩展,可以实现一个文件目录的拷贝。一旦进行文件文件目录的拷贝,还需要拷贝所有子目录中的文件。

范例:文件夹拷贝

package cn.ren.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		if (args.length != 2) {
			System.out.println("命令执行错误,执行结构:java JavaAPIDemo 拷贝的源文件路径  目标文件路径");
			System.exit(1);
		}
		long start = System.currentTimeMillis();
		FileUtil fu = new FileUtil(args[0], args[1]);
		
		if (new File(args[0]).isFile()) { // 文件拷贝

			System.out.println(fu.copy() ? "文件拷贝成功" : "文件拷贝失败");
		} else { // 既有文件,又有目录目录拷贝
			System.out.println(fu.copyDir() ? "目录拷贝成功" : "目录拷贝失败");
		}
		long end = System.currentTimeMillis();
		System.out.println("拷贝花费时间:" + (end - start));
	}
}

class FileUtil { // 文件操作类
	private File srcFile; // 源文件路径
	private File desFile; // 目标文件路径

	public FileUtil(File srcFile, File desFile) { // 构造方法一
		this.srcFile = srcFile;
		this.desFile = desFile;
	}

	public FileUtil(String src, String des) { // 构造方法二
		this(new File(src), new File(des));
	}

	public boolean copyDir() throws Exception {
		try {
			this.copyImpl(this.srcFile);
			return true ;
		} catch (Exception e) {
			return false;
		}
	}

	private void copyImpl(File file) throws Exception { // 递归操作
		if (file.isDirectory()) { // 是目录
			File result[] = file.listFiles(); // 列出全部目录组成
			if (result != null) { // 避免拒绝访问文件
				for (int x = 0; x < result.length; x++) {
					copyImpl(result[x]);
				}
			}
		} else { // 是文件
			// this.copyImpl(file);
			String newFilePath = file.getPath().replace(this.srcFile.getPath() + "\\", "") ;
			File newFile = new File(this.desFile, newFilePath) ; // 拷贝的目标的路径, 注意这里用的是"," ,构造方法自己组合
			this.copyFileImpl(file, newFile) ;
		}
	}

	private boolean copyFileImpl(File srcFile, File desFile) throws Exception {
		if (!desFile.getParentFile().exists()) { // 目标文件父目录不存在
			desFile.getParentFile().mkdirs(); // 创建父目录
		}
		InputStream input =  null ;
		OutputStream output = null ;
		try {
			input = new FileInputStream(srcFile) ;
			output = new FileOutputStream(desFile) ;
			input.transferTo(output) ; // 转存
			return true ;
		} catch (Exception e) {
			throw e ;
		} finally {
			if (input != null) {
				input.close() ;
			}
			if (output != null) {
				output.close();
			}	
		}
	}

	public boolean copy() throws Exception { // 文件的拷贝处理
		if (!this.srcFile.exists()) { // 源文件存在
			System.out.println("源文件不存在");
			return false;
		}		
		return this.copyFileImpl(this.srcFile, this.desFile);
	}

}

本程序是IO操作的核心代码,必须理解。

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值