目录
1. 文件概述
1.1 狭义和广义上的文件
文件,File这个概念,在计算机中,也是一词多义的。
狭义的文件:一般指的是硬盘中的文件和目录,例如在硬盘中的 .pdf .rar .png 等这类文件,还有放置这些格式文件的文件夹(目录),这些存储在硬盘中的格式文件和文件夹(目录),便是狭义上的文件。
广义的文件:泛指计算机中很多的软硬件资源。在操作系统中,把很多的硬件设备和软件资源抽象成了文件,按照文件的方式来统一管理。比如网络设备中的网卡,在后续的网络编程中,操作系统对于网卡也是作为一种文件来操作。
1.2 文件的路径
每一个文件,在硬盘中都有一个自己对应的文件路径,例如 d:/dog.jpg 这便是一个文件路径。表示一个文件路径的时候,可以用 / 也可以用 \ 来分割不同的目录级别。(不过这里一般建议优先写 / ,如果使用 \ ,很容易形成转义字符,这就带来了不必要的麻烦)
表示路径的方式有两种风格:
1. 绝对路径:以 c: d: 盘开头的路径。
2.相对路径:以当前所在的目录作为基准,以 . 或者 .. 开头( . 有时候可以省略),来找到指定路径。 当前所在的目录指的是工作目录:每个程序运行的时候,都会有一个工作目录。
例如在 cmd 中运行 calc 的时候
此时的工作目录为 D:\test
此时的工作目录为 D:\test\aaa
以下面的 C:\Program Files\Java\jdk1.8.0_192\bin 作为工作目录为例,如果要找到java.exe文件,则相对路径可以表示为 ./java.exe ,此处的 . 就表示C:\Program Files\Java\jdk1.8.0_192\bin;
而使用绝对路径:C:\Program Files\Java\jdk1.8.0_192\bin\java.exe
如果还是以当前的工作目录:C:\Program Files\Java\jdk1.8.0_192\bin ,来找到下图中的 src.zip 文件(src.zip 处于跟bin文件同一目录下) ,则相对路径可以表示为:../src.zip ,此处的 .. 表示当前工作目录的上一级目录:C:\Program Files\Java\jdk1.8.0_192;
绝对路径就可以表示为:C:\Program Files\Java\jdk1.8.0_192\src.zip
这里对上述 cmd 中不同路径都可以运行 calc 程序进行一个简单的解释:在命令行下,直接输入某个程序的名字,本质上是操作系统去 PATH 环境变量里查找的,calc本身就在 PATH 下,所以可以直接运行。
但如果是自己安装的程序,例如 qq或者微信,默认是不可以的,因为在环境变量 PATH 中是没有这个程序的,但是如果手动添加 qq.exe 进去,也就可以通过命令行在不同工作目录下去运行该程序了。
1.3 文件的类型
文件可以分为
1. 文本文件(存的是文本,字符串)这个文本文件里存的数据,一定是合法的字符,都是在你指定字符编码的码表之内的数据。每一个字符,都是通过一个数字来表示。(类似于ASCII,或者utf-8每一个汉字都会有一个对应的数字)
2. 二进制文件(存的是二进制数据,不一定是字符串了)没有任何限制,可以存储任何你想要存储的数据。
文本文件使用记事本打开,通常都是正常的文本显示;二进制文件使用记事本打开,通常都是显示乱码 。实际写代码的时候,这两类文件的处理方式略有差别。
2. 针对文件系统的操作
文件系统的操作就涉及到文件的创建,删除和重命名等。
在Java的标准库中,提供了一个File的类,来完成对某一路径上的文件进行操作。
构造方法 (其中的参数pathname表示的路径,可以是绝对路径或者相对路径)
签名 | 说明 |
File(File parent, String child)
|
根据父目录
+
孩子文件路径,创建一个新的
File
实例
|
File(String pathname)
|
根据文件路径创建一个新的
File
实例,路径可以是绝对路径或者相对路径
|
File(String parent, String child)
|
根据父目录
+
孩子文件路径,创建一个新的
File
实例,父目录用路径表示
|
方法(对于这些方法,会使用就行)
利用上述三种构造方法,来创建test.txt , test2.txt , test3.txt 三个文件,并进行File类的判断操作:
import java.io.File;
import java.io.IOException;
public class IODemo2 {
public static void main(String[] args) throws IOException {
File file = new File("./test.txt"); //这里的 . 表示默认工作路径
file.createNewFile();
File file1 = new File(".","test2.txt");
file1.createNewFile();
File file2 = new File(".");
File file3 = new File(file2,"test3.txt");
file3.createNewFile();
System.out.println(file.exists()+" "+file1.exists()+" "+file3.exists());
System.out.println(file.isFile()+" "+file1.isFile()+" "+file3.isFile());
System.out.println(file.isDirectory()+" "+file1.isDirectory()+" "+file3.isDirectory());
System.out.println(file.isHidden()+" "+file1.isHidden()+" "+file3.isHidden());
System.out.println(file.isAbsolute()+" "+file1.isAbsolute()+" "+file3.isAbsolute());
System.out.println(file.canRead()+" "+file1.canRead()+" "+file3.canRead());
System.out.println(file.canWrite()+" "+file1.canWrite()+" "+file3.canWrite());
System.out.println(file.canExecute()+" "+file1.canExecute()+" "+file3.canExecute());
}
}
应该注意的是:在new File的时候,并没有真正的创建了文件,而在调用 createNewFile() 的时候才真正去创建文件。
File的删除操作-delete-deleteOnExist:
import java.io.File;
public class IODemo3 {
public static void main(String[] args) {
File file = new File("./test.txt");
File file1 = new File("./test2.txt");
File file2 = new File("./test3.txt");
file.delete();
file1.delete();
file2.delete();
}
}
deleteOnExist() 这个方法的功能是在程序退出的时候,自动删除文件。
这也就类似于在编辑文档时出现的临时文件,当程序需要保证在退出时把临时文件删除的时候就可以用该方法。
临时文件的作用:在打开一个word文件,进行编写的时候,同级目录下就会生成一个临时文件,来实时保存我们实时编写的内容,包括已经保存了或者未保存的内容。这样是为了防止发生突发情况,word文件关闭但是没有保存,此时临时文件就发挥作用了,临时文件里实时保存着我们之前编辑的内容,在再一次打开word文件的时候,就会提示我们是否要恢复之前的编辑状态。
File类的获取操作:
import java.io.File;
import java.io.IOException;
public class IODemo1 {
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");
System.out.println(file.getName());
System.out.println(file.getParent());
System.out.println(file.getPath());
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}
}
mkdir - mkdirs创建目录:
import java.io.File;
//创建目录
public class IODemo4 {
public static void main(String[] args) {
//创建一级目录
File dir = new File("./text");
dir.mkdir();
//创建多级目录
File dir2 = new File("./text/aaa/bbb");
dir2.mkdirs();
}
}
此处要注意,对于这种多层目录,不可以一次性删除,要删除的话应该进行递归删除,先删除最内层的 bbb文件,后删除 aaa文件,最后才可删除 text文件 。
renameTo重命名:
import java.io.File;
public class IODemo5 {
public static void main(String[] args) {
//重命名
File file = new File("./text");
File file1 = new File("./testSSS");
file.renameTo(file1);
}
}
list() 和 listFiles()方法:
import java.io.File;
import java.lang.reflect.Array;
import java.util.Arrays;
public class IODemo13 {
public static void main(String[] args) {
File file = new File(".");
System.out.println(Arrays.toString(file.list()));
System.out.println(Arrays.toString(file.listFiles()));
}
}
3. 针对文件内容的操作(文件的读和写)
3.1 IO流对象
对于文件内容的操作,是使用 “流对象” 来进行操作的,所谓的 “流对象” 是一种形象的比喻。正如水流,生生不息,绵延不断,如果要接100ml水,可以分一次接完100ml,可以一次接50ml两次接完,也可以一次接10ml十次接完,也就是说,操作是可控的。
类比于文件的读和写,针对文件内容的操作,是可以一次操作一个字节/字符,也可以一次操作多个字节/字符,就像水流一样,因此就把读写文件的相关对象,称为流对象。
同理,如果是一个羽毛球桶,每次从桶里拿羽毛球,一次也只能拿一个,也就不再是流了。
针对文件的读和写,Java标准库中提供了两组类和接口,分别针对两类文件来进行读和写。一组是字节流对象,是针对二进制文件进行读和写的;一组是字符流对象,是针对文本文件进行读和写的。
对于二进制文件的读和写,使用字节流对象,用到的是 InputStream读 和 OutoutStream写 ;对于文本文件的读和写,使用字符流对象,用到的是 Reader读 和 Writer写 ;需要注意的是,这四个类都是抽象类,是不可以直接new的。但要使用还需要具体的实现类,关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,因此使用 FileInputStream FileOutPutStream FileReader FileWriter 来分别对应。
对文件进行写操作称为输出流,对文件进行读操作称为输入流。
需要补充的是:计算机中的读(输入流)和写(输出流)的方向,是以 CPU 为中心,来看待这个方向的。
内存更接近于 CPU ,硬盘离 CPU 更远。以 CPU 为中心,数据,朝着 CPU 的方向流向,就是输入,所以就把数据从硬盘到内存的操作,称为读操作(input);数据,远离 CPU 的方向流向,就是输出,所以就把数据从内存到硬盘的操作,称为写操作(output)。
(文件都是存储在硬盘上的)
虽然上述涉及到的类比较多,但是这些类的使用方法是比较固定的,核心就是四个操作:
1.打开文件。(构造对象)
2.读操作(read)=> 针对 InputStream / Reader
3.写操作(write)=> 针对 OutputStream / Writer
4.关闭文件(close)
下文进行介绍。
3.2 文件的读操作(字节流)
修饰符及
返回值类
型
|
方法签名
|
说明
|
int
|
read()
|
读取一个字节的数据,返回
-1
代表已经完全读完了
|
int
|
read(byte[] b)
|
最多读取
b.length
字节的数据到
b
中,
返回实际读到的数量
;-1
代表已经读完了
|
int
|
read(byte[] b, int off, int len)
|
最多读取
len - off
字节的数据到
b
中,放在从
off
开始,
返回实际读到的数量
;-1
代表以及读完了
|
void |
close()
|
关闭字节流
|
应该注意的是,read() 一个参数和三个参数版本中的 byte[] 是一个 “输出型参数” ,也就是需要先构造好这个字节数组, 再把读到的内容填充到参数的这个字节数组中。
第一种 read() 读取的是一个字节,按理说,是会返回一个 byte 类型,但实际上是返回的是int。这里除了要表示一个 byte 里的 0-255 (-128 - 127)这样的情况之外,还需要考虑一种特殊情况-1,-1 表示读取文件结束,也就是读取到文件末尾了。
签名
|
说明
|
FileInputStream(File file)
|
利用
File
构造文件输入流
|
FileInputStream(String name)
|
利用文件路径构造文件输入流
|
当使用字节流去读取一个txt文件的时候(使用read的第一个版本:一次读一个字节):
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class IODemo6 {
public static void main(String[] args) throws IOException {
// 创建 InputStream 对象的时候,使用绝对路径或者相对路径,都是可以的,也可以使用已经创建的 File 对象
InputStream inputStream = new FileInputStream("d:/text.txt");
//进行读操作
while (true){
int b = inputStream.read();
if (b == -1){
//读取完毕
break;
}
//当读到的是 英文字符 或者数字的时候,b和(byte)b 是一样的。
// 说明在进行io操作的时候,一个数字或者 英文字符 转换成byte的时候是以 ASCII 的形式出现的
// 当读到的是 中文或者中文字符的时候,b和(byte)b 一般是不一样的。
// 文字一般是2个字节或者3个字节,因此读取的时候是把一个文字分为一个字节一个字节来读取,若该字节超出一个字节255的范围,会呈现出 负数 的形式。
System.out.println(b+":" + (byte)b); // 因为b的返回值为int类型
//如果文件内容是文字,使用十六进制输出 观察一下码表(utf-8)每个汉字对应三个字节
//如果是gbk,那一个汉字对应两个字节
//因此使用字节流也可以打开文本文件,打开的内容也就是字节
//System.out.printf("%x \n",(byte)b);
}
inputStream.close();
}
}
通过上述代码代码和运行可以看出,当前的 txt文件是文本文件,使用字节流也是可以读取的,但从效率上来讲,并不推荐,对于文本文件,使用字符流,会更方便。
当使用字节流去读取一个txt文件的时候(使用read的第二个版本:一次读若干个字节):
在上文介绍时,我们说过,read 带参数版本中的 byte[] 是一个 “输出型参数” ,因此需要在调用前提前构造好一个数组,然后在进行传参操作的时候,将构造好的数组 byte[] 交给read 方法,便可以让 read 方法内部对 byte[] 这一数组进行填写。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class IODemo6 {
public static void main(String[] args) throws IOException {
// 创建 InputStream 对象的时候,使用绝对路径或者相对路径,都是可以的,也可以使用已经创建的 File 对象
InputStream inputStream = new FileInputStream("d:/text.txt");
//一次读完若干个字节
while (true){
byte[] buffer = new byte[2];
int len = inputStream.read(buffer);
System.out.println("len: " + len);
if (len == -1){
break; // -1表示读取完毕
}
for (int i = 0; i < len; i++) {
System.out.printf("%x\n",buffer[i]);
}
}
inputStream.close();
}
}
此处应注意理解 read 的行为和返回值:read 方法会尽可能将参数传进来的数组给填满,例如上述代码中定义的数组长度为2,文件内容依旧为:“你好” ,文件一共有6个字节(utf-8中一个文字三个字节),因此每一次只能读取两个字节,此时read的返回值是实际读取到的字节数量,也就是返回2,这样连续读取三次,每一次都是读取2个字节,返回值都是2 ,将文件内容读取完后,再次读取时,已经无内容了,就返回 -1 结束循环。再比如将数组长度设为4的话,第一次读取4个字节,返回4,第二次读取剩余的2个字节,返回2,最后一次读取已经读取结束了,返回 -1 。
也就可以理解为,设置的 参数 byte[] 的长度是希望读取的长度,而 read 的返回值是实际读取的长度。注意:返回 -1,是在最后读完了,一个字节都读取不到的时候,才会返回 -1 。只要有字节可以读取,就是返回读取的字节数。
同时要认识到,在每一次的读取,都会覆盖前一次读取的数据。因此对于文件的操作,基本都是一边读,一遍操作,处理好一部分,再处理好下一部分。因为文件是在磁盘上的,内容可能会很大,甚至会超出内存的上限,因此想要处理文件的数据,不可能会把整个文件都读取到内存中再处理,而是采取边读取,边处理的方式。
在执行完程序后,应该手动将流对象 close。
3.3 文件的写操作(字节流)
OutputStream 概述
void
|
write(int b)
|
写入要给字节的数据
|
void
|
write(byte[]
b)
|
将
b
这个字符数组中的数据全部写入文件
中
|
int |
write(byte[]
b, int off,
int len)
|
将
b
这个字符数组中从
off
开始的数据写入文件
中,一共写
len
个
|
void
|
close()
|
关闭字节流
|
void
|
flush()
|
重要:我们知道
I/O
的速度是很慢的,所以,大多的
OutputStream
为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置,调用 flush
(刷新)操作,将数据刷到设备中。
|
OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,所以使用 FileOutputStream。
对于 OutputStream 来说,默认情况下,打开一个文件,会先清空文件原有的内容。这样的话,在下述代码中,之前文件里存储的 “你好” 就被清空了。如果不想被清空,流对象还提供了一个“追加写”对象,通过这个就可以实现不清空文件,就把新内容添加到文件中。
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
//字节流
public class IODemo7 {
//进行写文件
public static void main(String[] args) throws IOException {
OutputStream outputStream = new FileOutputStream("d:/text.txt");
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
outputStream.close();
}
}
上述代码运行结果也就将文件中原来的 “你好” 换成了 “abcd” 。在程序执行末尾依然要进行流对象的 close。
3.4 close的重要性(内部分析)和优化程序
此处的close,也可以理解为文件关闭,在前面多线程的文章中,我们也了解到了:线程在内核里,是使用 PCB 这样的数据结构来表示的,一个线程对应一个 PCB ,一个进程可以包含多个线程或者一个线程,因此一个进程也可以对应多个PCB,也可以对应一个PCB。
而在PCB中,有一个重要的属性:文件描述符表,一个进程中的多个线程是共享一个文件描述符表的。文件描述符表相当于一个数组,记录这个进程中打开了哪些文件。
因此,每次打开文件操作,就会在文件描述符表里申请一个空间,把信息放进去,每次关闭文件,就会把对应的空间释放。所以,如果没有手动 close 对应的表项,没有及时释放,意味着文件描述符表很快就会被占满,因为这个数组,不可以自动扩容,是存在上限的。占满了之后,在再次打开文件的时候,就会打开失败。(文件描述符表最大长度,一般不同系统上都不太一样,但是基本就 几百到几千个左右)
(虽然 Java中有 GC,会在回收 outputStream 对象的时候去完成这个释放操作,但是这个 GC 操作不一定及时。)
因此,基于上述讲解,手动 close 也难免会有忘记的时候,也就引申出更好的写法:
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
//字节流
public class IODemo7 {
//进行写文件
public static void main(String[] args) throws IOException {
//更好的写法
//这种写法虽然没有显示的写close,实际上会执行的,只要try语句块执行完毕,就可以自动执行到close
try (OutputStream outputStream = new FileOutputStream("d:/text.txt")){
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
}
}
}
这种写法虽然没有显示的写 close ,实际上是会执行的,只要 try 语句块执行完毕,就可以自动执行 close。这种语法在 Java中也被称为 try with resource。
但也不是哪个对象放在 try() 里都可以自动释放的,得满足一定的要求:
实现了 Closeable 接口的类,才可以放到 try() 中被自动关闭。
刷新缓冲区
这里补充一个常见的问题:有的时候,会发现,写文件的内容,并没有真正出现在文件里,这个就很可能是缓冲区在搞问题。
像平常里正常的写操作,其实是先写到缓冲区里。(缓冲区有很多种形态,在我们自身的代码里有缓冲区;标准库里也有缓冲区;在操作系统也可以有自身的缓冲区)也就是说,写操作执行完了,内容可能还在缓冲区里,并没有进入硬盘。
而 close 操作就会触发缓冲区的刷新(刷新操作,就会把缓冲区内的内容写到硬盘里)
处理 close 操作,flush 方法也是可以起到刷新缓冲区的效果。
3.5 文件的读操作和写操作(字符流)
字符流和字节流,用法基本相似,因此这里就简单使用代码演示。
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
//字符流
public class IODemo8 {
public static void main(String[] args) {
try (Reader reader = new FileReader("D:/text.txt")){
while (true){
int ch = reader.read();
if (ch == -1){
break;
}
System.out.println("" + (char)ch);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class IODemo9 {
public static void main(String[] args) {
try (Writer writer = new FileWriter("D:/text.txt")){
writer.write("hello world1");
//手动刷新缓冲区
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3.6 Scanner 搭配流对象进行使用
Scanner scanner = new Scanner(System.in)中 System.in 本质上是一个输入流对象。因此换成其他输入流对象也是没问题的。
因此我们可以写出以下代码:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class IODemo10 {
public static void main(String[] args) {
//Scanner scanner = new Scanner(System.in); // System.in 本质上是输入流对象,因此如果是其他的输入流对象,也可以作为参数
try(InputStream inputStream = new FileInputStream("d:/text.txt")){
Scanner scanner1 = new Scanner(inputStream);
// 此时读取的内容就是从 文件 中进行读取了
String str = scanner1.nextLine();
System.out.println(str);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Scanner 的 close 本质上是要关闭内部包含的 System.in 流对象,此时内部的 inputStream 已经被try 关闭了,因此里面的 Scanner 不关闭也没事。
4. 文件操作案例
扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件。
给定一个目录里,目录中会包含有很多的文件和子目录,因此对于子目录,还要用到递归思想。使用递归遍历目录。
要注意使用 listFiles() ,得到一个文件数组;而不用 list ,虽然list 也可以得到文件名,但只是 String 类型的文件名,并不方面后续操作。
import java.io.File;
import java.util.Scanner;
//扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
public class IODemo11 {
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
//让用户输入一个指定搜索的目录
System.out.println("请输入要搜索的路径");
String basePath = scanner.next();
//针对用户输入进行简单判定
File root = new File(basePath);
// 看是否是目录
if (!root.isDirectory()){
// 路径不存在,或者只是一个普通的文件,此时无法进行搜索
System.out.println("输入目录有误!");
return;
}
// 再让用户输入一个要删除的文件名
System.out.println("请输入要删除的文件名");
//此处要使用 next(针对换行符,空格这些) ,而不使用 nextLine(只针对换行符)
String nameToDelete = scanner.next();
// 针对指定路径进行扫描,递归操作
// 先从根目录出发
// 先判定一下, 当前的这个目录里, 看看是否包含咱们要删除的文件. 如果是, 就删除; 否则就跳过下一个.
// 如果当前这里包含了一些目录, 再针对子目录进行递归.
scanDir(root,nameToDelete);
}
private static void scanDir(File root, String nameToDelete) {
System.out.println("[scanDir] " + root.getAbsolutePath());
// 1. 先列出 root 下的文件和目录
File[] files = root.listFiles();
if (files == null){
// 当前目录下没东西,是一个空目录
// 结束执行
return;
}
// 2. 遍历当前列出的结果
for (File file : files) {
if (file.isDirectory()){
// 如果是目录,那就进一步递归
scanDir(file,nameToDelete);
}else {
// 如果是普通文件,则判定是否要删除
if (file.getName().contains(nameToDelete)){
System.out.println("确认是否要删除 " + file.getAbsolutePath() + "嘛");
String choice = scanner.next();
if (choice.equals("y") || choice.equals("Y")){
file.delete();
System.out.println("删除成功");
}else {
System.out.println("取消删除");
}
}
}
}
}
}
进行普通文件的复制
很简单,就是把第一个文件的内容按照字节依次读取,把结果写入到另一个文件中。要注意,被拷贝的文件,是先前不存在的。
OutputStream 在写文件的时候,文件不存在,就会自动创建,InputStream不行,文件不存在,就抛异常。
(try 语法支持包含多个流对象,多个流对象之间用 ;分开)
import java.io.*;
import java.util.Scanner;
// 进行普通文件的复制
public class IODemo12 {
public static void main(String[] args) {
// 输入两个路径 : 源头(从哪拷贝) 和 目标(拷贝到哪)
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要拷贝哪个文件:");
String sourPath = scanner.next();
System.out.println("请输入要拷贝到哪个文件");
String destPath = scanner.next();
File sourFile = new File(sourPath);
if (!sourFile.isFile()){
// 如果不是一个文件 (是个目录或者不存在)
// 直接返回
System.out.println("当前输入路径有误");
return;
}
File destFile = new File(destPath);
if (destFile.isFile()){
// 如果已经存在,是不能进行拷贝的,会覆盖之前内容
System.out.println("当前输入目标路径有误");
return;
}
//进行拷贝操作
try (InputStream inputStream = new FileInputStream(sourFile);
OutputStream outputStream = new FileOutputStream(destFile)){
// outputStream在文件不存在的时候会自动创建!!!
// 进行读文件操作
while (true){
int b = inputStream.read();
if (b == -1){
break;
}
outputStream.write(b);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}