Java文件IO及其案例分析

目录

1. 文件概述

1.1 狭义和广义上的文件

1.2 文件的路径

 1.3 文件的类型

2. 针对文件系统的操作

3. 针对文件内容的操作(文件的读和写)

3.1 IO流对象

 3.2 文件的读操作(字节流)

3.3 文件的写操作(字节流)

3.4 close的重要性(内部分析)和优化程序

3.5 文件的读操作和写操作(字符流)

3.6 Scanner 搭配流对象进行使用

4. 文件操作案例


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 文件的读操作(字节流)

InputStream 概述
修饰符及 返回值类
方法签名
说明
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 概述
签名
说明
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);
        }
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PlLI-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值