Java文件IO

本篇主要介绍Java中文件IO的相关内容

目录

一、什么是文件

二、操作文件

三、读写文件

流对象

字符流

字符输入流

字符输出流

字节流

字节输入流

Scanner搭配流对象

打印流

 注意事项

四、IO实战


一、什么是文件

文件是存储在计算机存储设备上的数据单元,一个文本,一张图片,一个音频....这些都可以被称为是文件。文件的数据格式非常丰富,.jpg、.txt 、.img 、.mp3.....,这些数据格式通常可以规为两种,一种是文本文件,一种是二进制文件。文本文件储存的是一个一个字符,而二进制文件储存的则是一个一个二进制数字。我们如何区分这两种文件呢?

通过记事本打开一个文件,如果里面是下面这种我们能够读懂的文字,那这就是一个文本文件。

如果打开的是一堆我们看不懂的文字和符号,那这个文件就是二进制文件。

文件的路径有两种,一种是以盘符为起始位置的绝对路径,另一种则是以当前目录为起始位置的相对路径。

我们通过文件管理器打开一个文件,在最上方的导航框内显示的就是绝对路径,\ 代表下一级路径(Windows  \  / 都代表下一级路径), 代表在哪个盘里

而以 . 或者 / 为起始的路径就是相对路径,其中 . 代表当前路径, .. 代表上一级路径,同理再加一个点就代表更上一级的路径,以 / 开头是对单个 . 的省略写法。

二、操作文件

在Java中,通过File类来表示一个文件或者文件夹,File类位于Java.io包下,File类的构造方法如下

构造方法作用
File(String pathname)根据文件路径打开文件
File(String parent,String child)根据父路径和子路径来打开文件
File(File parent.String child)根据父文件和子路径打开文件

传入的文件路径可以是绝对路径,也可以是相对路径,如果是相对路径,在Idea中以当前项目所在的目录为工作目录,在其他环境则是以当前执行代码的命令行所在目录作为工作目录。如果路径所表示的文件或者文件夹不存在,则可以通过File的相关方法进行创建。

在File类中,封装了许多操作文件和获取文件信息的方法,例如:

获取文件信息的方法有:

方法名作用
getParent()获取文件的父目录
getName()获取文件名称
getPath()获取文件路径
getAbsolutePath()获取文件绝对路径

通过代码测试以下这些方法

运行结果:

对文件属性进行判断的方法(返回值均为boolean类型)有:

方法作用
exist()判断文件是否存在
isDirectory()判断文件是否为文件夹
isFile()判断是否为文件
isHidden()判断文件是否为隐藏文件
isAbsolute判断在创建File时传入的路径是否为绝对路径
canRead()判断用户是否对文件有可读权限
canWrite()判断用户是否对文件有可写权限
canExecute()判断用户是否对文件有执行权限

代码测试:

执行结果

对文件的创建和删除的方法有

方法作用
boolean createNewFile()创建File所代表的文件
boolean mkdir()创建File所代表的目录(可以是多级目录)
boolean delete()删除File所代表的文件
void deleteOnExist()在代码运行结束后删除File所代表的文件

这里我们来演示一下删除前面测试代码里的test.doc 文件

执行代码前

执行后

然后我们再通过createNewFile再重新创建一个retest.doc(使用这个方法需要处理IO异常)

执行前

执行后

接下来我们再试试创建一个tests文件夹

执行前:

执行后:

其他方法:

方法作用
String[] list()获取目录下所有文件的文件名
File[] listFiles()以File对象的形式获取目录下的所有文件
booelan renameTo()修改文件名

代码示例

运行结果:

打开文件管理器可以发现tests1已经成功改名了

三、读写文件

流对象

Java中读写文件是通过流对象来完成,什么是流对象呢?

在解释什么是流对象之前,我们先来了解一下什么是流。流,和水流类似,一点点的流动,并且延绵不断,具体可以理解为数据以字节/字符的方连续不断的从一个地方传入到另一个地方,流传输的数据数量并不确定,你可以一次流动很多字节/字符,也可以只流动一个字节/字符,就像水一样,你可以一次取很多,也可以一次只取一点。

而流对象就是对流进行封装后的对象。流通常分为两种,一种是输出(写)流,一种是输入(读)流,输入输出是以CPU为参照的,数据如果是从其他设备往靠近CPU的方向流动,那这就是输入流,如果是往远离CPU的方向流动,那就是输出流,例如从硬盘加载数据到内存,就是输入流,反过来就是输出流。因为通常认为,内存相较于硬盘更接近CPU.

因此流对象又可以根据流的不同分为输入流对象和输出流对象,并且流对象根据传输的数据的类型又划分出了以字符形式传输 的字符流和以二进制形式传输的字节流。(注意:进行流对象的相关操作时通常需要处理IO异常)

字符流
字符输入流

Java中的字符输入流为Reader,Reader是一个接口,他的实现类为FileReader,FileReader的构造方法为:

构造方法作用
FileReader(File file)根据源文件对象创建字符输入流
FileReader(File file,Charset charset)根据源文件对象创建字符输入流,并指定字符集
FileReader(String fileName)根据源文件路径创建字符输入流
FileReader(String fileName,CharSet charset)根据源文件路径创建字符输入流,并指定字符集

Reader的主要方法如下:

方法作用
int read()读取一个字符数据,返回值为读到的字符值,读到-1代表全部读完
int read(char[] b)一次最多读取b.len个字符,返回值为读到的字符数,读到-1代表读完
int read(char[] b,int off,int len)一次最多读取len - off个字符,返回值为读到的字符数,并从b的off位置开始放,读到-1代表读完
void close()关闭流
字符输出流

Java中的字符输出流为Writer,它的实现类为FileWriter,构造方法为:

构造方法作用
FileWriter(File file)根据文件对象创建字符输出流
FileWriter(File file ,boolean append)根据文件对象创建字符输出流,并设置是否追加写
FileWriter(String filePath)根据文件路径创建字符输出流
FileWriter(String filePath,boolean append)根据文件路径创建字符输出流,并设置是否追加写

Writer的方法如下:

方法作用
void write(int b)写入一个字符数据
void write(char[] b)将数组b的数据全部写入
int write(char[] b , int off ,int len)将数组b从off到len的数据写入
void close()关闭字符流
void flush()冲刷缓冲区

(众所周知,一次IO操作是非常耗时的,大多数输出流为了提升效率,并不会直接将数据写到文件里,而是先将数据保存到一个特定的区域,直到这个区放满才会将数据一次性写到文件中,这个数据就是缓冲区,而flush就是将缓冲区的数据直接冲刷到文件中,执行close方法时也是会进行一次冲刷缓冲区的操作

接下来我们来实际使用一下这两个字符流

准备两个文本文件,text1,text2,往text1中写入数据"hello",然后通过ReaderFile读取text1的数据,并将读取到的数据写入到text2.

代码如下

(这里的buffer如果放满了,代表一次读取完成,当进行下一次读取,下一次读取的数据会覆盖buufer前面读取到的数据)

执行完后打开text2,也能到"hello"了

字节流
字节输入流

Java中的字节输入流为InputStrem,它的实现类为FileInputStrem,构造方法如下:

构造方法作用
FileInputStrem(File file)根据文件对象创建字节输入流
FileInputStrem(String filePath)根据文件路径创建字节输入流

InputStrem的主要方法如下:

方法作用
int read()读取一个字节的数据,返回值就是读取到的字节值,返回1代表读完
int read(byte[] b)一次最多读取b.len个字节,返回值为读到的字节数,读到-1代表读完
int read(byte[],int off,int len)一次最多读取len - off个字节,返回值为读到的字节数,并从b的off位置开始读,读到-1代表读完
void close()关闭流

(读取一个字节数据的无参方法的返回值是int ,明明返回的是byte类型的数据,为什么要用int来接收了,因为读到-1代表读完,而byte能表示的数据范围为-128到127,如果返回的是-1的话,将不能确定是读取完了还是真的读到了一个数据-1,而使用int就能解决这个问题,使用int接收byte的数据,会使原来能读到数据范围变成0 - 255,因此也就避免了上述问题,所以这里用int作为返回值,前面的字符流的read()用int作为返回值也是这个原因)

Java的字节输出流为OutputStrem,实现类为FileOutputStrem,构造方法为

构造方法作用
FileOutputStrem(File file)根据文件对象创建字节输出流
FileOutputStrem(File file ,boolean append)根据文件对象创建字节输出流,并设置是否追加写
FileOutputStrem(String filePath)根据文件路径创建字节输出流
FileOutputStrem(String filePath,boolean append)根据文件路径创建字节输出流,并设置是否追加写

OutputStrem的主要方法如下

方法作用
void write(int b)写入一个字节数据
void write(byte[] b)将数组b的数据全部写入
int write(byte[] b , int off ,int len)将数组b从off到len的数据写入
void close()关闭流
void flush()冲刷缓冲区

 接下来我们实操一下字节流的使用

将一张二进制的图片文件复制到另一个路径下

代码如下:

打开文件可以发现已经有imgs.webp了

(注意:我的电脑里原本是没有imgs.webp的,而为什么会写入成功呢,是因为输出流在写入文件时如果发现文件不存在,会自动依据构造方法传入的文件对象,或者文件路径创建一个目标文件,然后再进行数据写入)

Scanner搭配流对象

在Java中是可以通过Scanner搭配流对象进行数据读取的,在我们平常使用Scanner时,需要传入一个System.in,这其实就是一个流对象,因此我们可以传入一个我们自定义的流对象来实现对文件的读取。

接下来我们来常试一下通过Scanner来读取我们前面的text1文件

代码如下

打印结果:

打印流

在Java中存在打印流,打印流对输出流进行了一个封装,能够实现更加方便的写文件操作。

打印流有两种,一种为PrintStrem(字节打印流),另一种为PrintWrite(字符打印流),他们的构造方法如下:

构造方法作用
PrintStrem(File file)/PrintWriter(File file)通过文件对象构建打印流

PrintStrem(OutputStrem out)/PrintWriter(Writer writer)

通过输出流对象构建打印流

PrintStrem(OutputStrem out,boolean autoFlush)/PrintWriter(Writer writer,boolean autoFlush)

通过输出流对象构建打印流,并设置是否自动刷新缓冲区

PrintStrem(String filePath, String charset)

根据文件路径创建打印流,并设置字符编码

通常情况下,更加推荐使用字符打印流,因为字符打印流封装了字符流,以字符为单位进行数据传输,相当于字节打印流更加灵活。

下面我们来使用打印流往前面的text2里写几个数据,代码如下

 执行完后可以发现,数据已经追加到text2了

 注意事项

在前面介绍进程的时候,我们了解过,每一个进程都会对应一个PCB,在PCB中有一个重要的部分,文件资源符标,它里面记录了进程所拥有的文件资源,我们在进程中每打开一个文件,都会往文件描述符表中占用一个位置,但文件描述符表的位置数量是有限的,这也就意味着当文件描述符表被占满后,是无法继续正常获取文件资源的,因此,我们在使用完一个文件资源后,应当及时手动进行释放,但我们不是有GC帮我们自动进行对象回收吗,为什么还要手动释放呢?因为GC回收对象并不是即时的,它存在一定延迟,我们并不能保证GC每次都能及时帮我们回收,所以还是建议自己手动进行释放。

在这里给大家提供一个小技巧,我们可以将创建流对象的代码写在try-catch中的try后面的括号里,在try-catch执行完毕后会自动将前面写在括号里的流对象进行释放,具体代码格式如下:

但要注意的是,不是每个对象都能被try自动释放,要被自动释放必须实现Closeable接口

四、IO实战

下面我们通过上面所介绍的文件与流对象的知识,来进行实战一下:

实战内容为实现多线程文件拷贝

源码如下:

import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class FileIOTest {
    //创建一个线程池来多线程的拷贝
    ExecutorService pools = Executors.newFixedThreadPool(8);
    //根据是否所有线程都已完成来判断是否拷贝完成
    CountDownLatch latch = null;
    //记录正在执行拷贝的线程数
    AtomicInteger count = new AtomicInteger(0);
    //实现将一个文件的内容拷贝到另一个文件
    public void copyFile (File surFile, File destFile){
        try (InputStream inputStream = new FileInputStream(surFile); OutputStream outputStream = new FileOutputStream(destFile)){
            byte[] bytes = new byte[1024];
            while (true){
                int k = inputStream.read(bytes);
                if (k == -1) break;
                outputStream.write(bytes);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    //将一个文件或文件夹或文件拷贝到另一个文件夹或文件夹
    public void copyS(String sur , String dest){
        latch = new CountDownLatch(1);
        File surFile = new File(sur);
        if (!surFile.exists()){
            System.out.println("拷贝的源文件不存在!");
            return;
        }
        File destFile = new File(dest);
        if (!destFile.exists()) destFile.mkdir();
        copy(surFile,destFile);
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("拷贝完成");

    }
    //拷贝的具体逻辑
    public void copy(File surFile , File destFile){

        if (surFile.isFile() && destFile.isFile()){
            pools.submit(new Runnable() {
                @Override
                public void run() {
                    count.getAndIncrement();
                    copyFile(surFile,destFile);
                    count.getAndDecrement();
                    if (count.get() == 0){
                        latch.countDown();
                    }
                }
            });
        }
        if (surFile.isDirectory() && destFile.isDirectory()){
              //罗列源文件夹的所有文件,如果是文件直接在目的文件夹中的同名文件(没有则创建)中进行拷贝,如果是文件夹,则递归的执行copy(),参数
            // 为此文件夹和目的文件夹中的同名文件夹(没有则创建)
            File[] list = surFile.listFiles();
            for (File cur:
                 list) {
                if (cur.isFile()){
                    //将拷贝文件的认务交给线程池来执行
                    pools.submit(new Runnable() {
                        @Override
                        public void run() {
                            count.getAndIncrement();
                            copyFile(cur,new File(destFile.getAbsoluteFile()+"/"+cur.getName()));
                            count.getAndDecrement();
                            //如果当前已经没有线程执行,则打表拷贝完毕,可以冲线
                            if (count.get() == 0){
                                latch.countDown();
                            }
                        }
                    });
                }
                else {
                    File destCur = new File(destFile.getAbsoluteFile()+"/"+cur.getName());
                   if(!destCur.exists()) destCur.mkdir();
                   //将对文件夹的拷贝交给线程池来执行
                    pools.submit(new Runnable() {
                        @Override
                        public void run() {
                            count.getAndIncrement();
                           copy(cur,destCur);
                            count.getAndDecrement();
                            if (count.get() == 0){
                                latch.countDown();
                            }
                        }
                    });
                }
            }
        }
    }
    public static void main (String[] args) throws IOException{
        FileIOTest fileIOTest = new FileIOTest();
       Scanner scanner = new Scanner(System.in);
        System.out.println("请输入拷贝的源文件");
        String sur = scanner.nextLine();
        System.out.println("请输入拷贝的目的文件");
        String dest = scanner.nextLine();
        fileIOTest.copyS(sur,dest);

    }

接下来我们随便拷贝一个文件测试一下,

控制台的输入如下:

然后我们再去拷贝的目的路径

可以发现拷贝成功了

  • 27
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值