Java文件操作

目录

文件

针对文件系统的操作

File

针对文件内容的操作

字节流

InputStream

FileInputStream

OutputStream

FileOutputStream

字符流

Reader

FilerReader

Writer

FileWriter

练习

练习1

练习2


文件

文件:在这里我们讨论的文件指的是硬盘上的文件。文件除了有保存的数据内容之外,还有部分信息,如文件名、文件类型等不作为文件的数据而存在,这部分信息可视为文件的元信息

文件的分类:一般将文件划分为文本文件(保存字符集编码的文本)和二进制文件(按照标准格式保存的非被字符集编码过的文件)

文本文件:按照字符串的方式来理解文件内容(文本文件里的二进制内容,都是表示的字符串),即文本文件中的内容,都是合法的字符(即字符编码中的字符)

二进制文件:二进制文件没有文本文件上述限制,可以存储任何数据

如何判断一个文件是文本文件还是二进制文件?

 我们可以借助记事本来判断,使用记事本打开一个文件,若看到的是正常的内容,则这个文件是文本文件;若是乱码,则是二进制文件

树形结构组织文件:文件系统是按照层级结构来组织文件的(也就是数据结构中学过的树形结构),这里的树是N叉树,每一个目录文件中可以有很多个子节点,而每一个普通文件(非目录文件)是一个叶子节点。

路径:操作系统中使用“路径”来描述一个具体文件的位置。例如:C:\Users\data\test.txt,表示从根节点出发(Windows系统是从盘符出发),一级一级往下走,直到找到目标文件,将中间经过的所有目录的名字串联起来,使用 / 或 \ 分隔

路径分隔符:分隔符的作用类似于标点符号,用来分隔路径列表中的文件名。上述C:\Users\data\test.txt中的\就是路径分隔符

在Linux中使用的路径分隔符是斜杠 /,而在windows中既可以使用 反斜杠 \ 也可以使用 /。由于在Java中,\ 是一个特殊的字符,被作为转义字符使用,因此,若我们使用 \ 分隔路径时,要使用 反斜杠符(\\)来表示字面意义上的 \ ,即(C:\\Users\\data\\test.txt)

路径的表示方式:

路径有两种风格的表示方式:

绝对路径:从树根节点出发(Windows则是从盘符开始)一层一层到目标文件(如C:\Users\data\test.txt)

相对路径:以当前所在目录为依据,找到目标文件。使用 . 表示当前所在目录,(当前在data目录,则 .\test.txt),..表示上层目录,(若目标文件为:C:\Users\t\data.txt,则相对路径为../t/data.txt)

Java中的文件操作主要分为两类

1. 针对文件系统的操作,如创建文件、删除文件等

2. 针对文件内容的操作,如读文件、写文件等

Java中针对文件系统的操作,使用 File 类来对一个文件(包括目录)进行抽象的描述(有File对象,并不代表该文件真实存在),这个类在 java.io 包中

io:即 input(输入) output(输出)

CPU为参照,数据从 硬盘 到 CPU 叫做 输入从 CPU 到 硬盘 叫做 输出

针对文件系统的操作

针对文件系统的操作通常是通过 File 类来实现的

File

我们通过学习File类中的常见属性、构造方法和方法来了解 File 类

属性:

修饰符及类型属性说明
static StringpathSeparator依赖于系统的路径分隔符,String类型的表示
static StringpathSeparator依赖于系统的路径分隔符,char类型的表示

使用的路径分隔符根据系统自动调整

构造方法:

构造方法说明
File(File parent, String child)通过父目录 + 孩子文件路径,创建一个File实例
File(String pathname)通过文件路径创建一个File实例,路径可以是绝对路径也可以是相对路径
File(String parent, String child)通过父目录 + 孩子文件路径,创建一个File实例,父目录用路径表示

方法:

返回值类型方法说明
StringgetParent()返回 File 对象的父目录文件路径
StringgetName()返回 File 对象的纯文件名称
StringgetPath()返回 File 对象的文件路径
StringgetAbsolutePath()返回 File 对象的绝对路径
StringgetCanonicaPath()返回 File 对象的修饰过的绝对路径
booleanexists()判断 File 对象描述的文件是否真实存在
booleanisDirectory()判断 File 对象代表的文件是否是一个目录
booleanisFile()判断 File 对象代表的文件是否是一个普通文件
booleancreateNewFile()根据 File 对象,自动创建一个空文件。创建成功后返回true
booleandelete()根据 File 对象,删除该文件。删除成功后返回true
voiddeleteOnExit()根据 File 对象,标记该文件将被删除,删除操作将会在JVM运行结束时进行
String[]list()返回 File 对象代表的目录下的所有文件名
File[]listFiles()返回 File 对象代表的目录下的所有文件,以File 对象表示
booleanmkdir()创建 File 对象代表的目录
booleanmkdirs()创建 File 对象代表的目录,如果是多级目录,则会创建中间目录
booleanrenameTo(File dest)进行文件改名
booleancanRead()判断用户是否对文件有可读权限
booleancanWrite()判断用户是否对文件有可写权限

我们先观察不同get方法的特点及它们之间的差异:

以相对路径的方式创建File对象:

package IO;

import java.io.File;
import java.io.IOException;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        File file = new File("./test");//并不要求该文件真实存在
        System.out.println(file.getParent());//父目录文件路径
        System.out.println(file.getName());//纯文件名
        System.out.println(file.getPath());//文件路径
        System.out.println(file.getAbsoluteFile());//绝对路径
        System.out.println(file.getCanonicalFile());//File对象修饰过的绝对路径
    }
}

运行结果:

以绝对路径的方式创建File对象: 

package IO;

import java.io.File;
import java.io.IOException;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        File file = new File("C:\\Users\\whisper\\Desktop\\code\\Java\\java\\test_1_21\\test");//并不要求该文件真实存在
        System.out.println(file.getParent());//父目录文件路径
        System.out.println(file.getName());//纯文件名
        System.out.println(file.getPath());//文件路径
        System.out.println(file.getAbsoluteFile());//绝对路径
        System.out.println(file.getCanonicalFile());//File对象修饰过的绝对路径
    }
}

运行结果: 

通过观察上述运行结果我们发现:当使用绝对路径创建 File对象时,其文件路径、绝对路径和File修饰过的绝对路径相同;而使用相对路径创建 File 对象时,则不同,文件路径表示创建的相对路径,绝对路径与 File 对象修饰过的绝对路径相比,包含.\

普通文件的创建:

package IO;

import java.io.File;
import java.io.IOException;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        File file = new File("./test.txt");
        System.out.println(file.exists());//判断文件是否存在
        System.out.println(file.isFile());//判断文件是否是普通文件
        System.out.println(file.isDirectory());//判断文件是否是目录
        boolean ret = file.createNewFile();//根据 File 对象,自动创建一个空文件
        System.out.println(ret);

    }
}

运行结果:

文件不存在,因此其既不是普通文件,也不是目录。正是由于文件不存在,因此可以根据 File 对象自动创建一个空文件(当文件存在时,则不能创建,不存在两个相同路径的文件)。

普通文件的删除:

package IO;

import java.io.File;

public class Demo3 {
    public static void main(String[] args) {
        File file = new File("./test.txt");
        boolean ret = file.delete();//删除文件
        System.out.println(ret);
    }
}

运行结果为:true

当我们在运行Demo2时创建了一个test.txt文件,因此该文件存在,能够删除,也可以使用deleteOnExit()方法,在JVM运行结束时删除该文件

目录的创建:

package IO;

import java.io.File;

public class Demo4 {
    public static void main(String[] args) {
        File file = new File("./a/b/c");
        boolean ret1 = file.mkdir();
        boolean ret2 = file.mkdirs();
        System.out.println(ret1);
        System.out.println(ret2);
    }
}

运行结果:

使用mkdik()方法时,若中间目录不存在,则无法成功创建目录,而mkdirs()则可创建多级目录

目录下的所有文件:

package IO;

import java.io.File;
import java.util.Arrays;

public class Demo5 {
    public static void main(String[] args) {
        File file = new File(".");
        File[] files = file.listFiles();//以 File 对象表示
        System.out.println(Arrays.toString(files));
        String[] strings = file.list();
        System.out.println(Arrays.toString(strings));
    }
}

运行结果:

 文件重命名:

package IO;

import java.io.File;

public class Demo6 {
    public static void main(String[] args) {
        File file = new File("test.txt");//要重命名的文件必须存在
        File dest = new File("dest.txt");//更改后的文件名不能存在
        System.out.println("file:" + file.exists());
        System.out.println("dest:" + dest.exists());
        System.out.println(file.renameTo(dest));
        System.out.println("file: " + file.exists());
        System.out.println("dest:" + dest.exists());
    }
}

运行结果:

针对文件内容的操作

在学习针对文件内容的操作之前,我们首先来来了解一下数据流的概念

什么是数据流?

数据流是一串连续的数据集合,就像水池里的水流,在一端水管供水(即写数据),在另一端水管出水(即读数据)。在写入数据时,可以一点一点或一段一段地写入,这些数据会按照先后顺序形成一个长的数据流,则在读取数据时,可以读取任意长度的数据

Java标准库对流进行了一系列的封装,提供了一系列类来完成这些工作,而这些类大体可以分为两大类:

1. 字节流:以字节为单位进行读写,一次最少读写一个字节(输入:InputStream 输出:OutputStream)

2. 字符流:以字符为单位进行读写,一次最少读写一个字符,例如,若使用UTF- 8,则一个汉字占3个字节,每次读写都得以一个汉字为单位来进行读写,不能一次读写半个字符(输入:Reader 输出:Writer)

字节流

我们首先来看读文件操作

InputStream

InputStream(输入流),程序从输入流读取数据(数据来源:键盘、文件、网络...)

常用方法:

返回值类型方法说明
intread()读取一个字节的数据,当返回-1时则表示已经读取完毕
intread(byte[] b)最多读取长度为b.length字节的数据到b数组中,返回实际读取到的数量,当返回-1时表示已经读取完毕
intread(byte[] b, int off, int len)最多读取 len 字节的数据到b数组中,从off下标开始存放数据,返回实际读取到的数量,当返回-1时代表已经读取完毕
voidclose()关闭字节流

InputStream是一个抽象类,要使用需要具体的实现类。InputStream的实现类有很多(基本可以认为不同的输入设备都可以对应一个InputStream类),在这里我们需要从文件中读取,因此使用的实现类为 FileInputStream

FileInputStream

构造方法:

构造方法说明
FileInputStream(File file)利用File构造文件输入流
FileInputStream(String name)利用文件路径来构造文件输入流

在读取文件内容时,步骤为:打开文件 读文件 关闭文件

由于我们还没有学习写文件操作,因此我们手动向文件中写入数据,再从文件中读取数据

我们可以通过刚才学习的createNewFile()方法在当前路径下创建一个新文件,再写入数据

        //在当前路径下创建文件
        File file = new File("./test.txt");
        file.createNewFile();
        //手动向文件中写入数据:abcdefg

我们首先一个字节一个字节地读取数据:

package IO;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo7 {
    public static void main(String[] args) throws IOException {
        //在当前路径下创建文件
        File file = new File("./test.txt");
        file.createNewFile();
        //手动向文件中写入数据:abcdefg

        //打开文件
        InputStream inputStream = new FileInputStream(file);
        //读文件
        while (true){//循环读取,直到读取完数据
            int n = inputStream.read();
            if(n == -1){//当返回值为-1时,
                break;
            }
            System.out.println(n);//打印读取到的数据
        }
        //关闭文件
        inputStream.close();

    }
}

运行结果:

由于我们是直接打印int类型的n,因此打印结果是字符的ASCII码值

read()的返回值类型是int,但其实际取值为:-1 - 255,当读取到文件末尾,继续读取就会返回-1,其他情况下取值为0 - 255

然而,在上述代码中还有一个隐藏的问题:由于上述代码是 按照打开文件、读文件、关闭文件的顺序执行的,因此未出现问题。但若在读取文件的过程中抛出异常或是根据逻辑执行 return 语句,此时close()就未执行到,就会造成文件资源泄露

关闭文件必须执行,因此我们可以使用finally,即:

Java的try操作还提供了一个版本:try with resources 

try ( InputStream inputStream = new FileInputStream(file))//一旦代码出了try代码块,try就会自动调用inputStream的close()方法

注:只有实现了Closeable接口的类,才能放到try()中

也可以使用 read(byte[] b)方法,一次读取多个字节,并将读取到的数据放入b数组中:

package IO;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo7 {
    public static void main(String[] args) throws IOException {
        try ( InputStream inputStream = new FileInputStream("./test.txt")){
            //读文件
            while (true){//循环读取,直到读取完数据
                byte[] buffer = new byte[1024];
                int n = inputStream.read(buffer);
                if(n == -1){//读取完毕
                    break;
                }
                for (int i = 0; i < n; i++) {
                    System.out.printf("%c", buffer[i]);
                }
            }
        }
    }
}

运行结果:

默认情况下,read会将数组填满,但当文件剩余长度不足以填满数组时,返回值就会告诉我们实际填充了多少个字节

而 read(byte[] b, int off, int len) 则是填充数组的一部分,从off下标开始填入数据,填充len长度数据,若文件剩余长度小于len,此时返回值也会告诉我们实际填充了多少个字节

运行结果:

对比以上三种读取数据方式,相比于一次读取一个字节的数据,一次读取多个字节的IO次数更少,性能更好。

以字节为单位进行读写,是否能够读取中文?

我们将test.txt中内容换成中文: 

运行结果:

注:在写入中文时使用的是UTF-8编码,且在String中使用的编码也为UTF-8,因此可以正确读取数据,若我们将编码方式改为“GBK”,则不能正确读取出数据

String s = new String(buffer, 0, n,"GBK");

因此,我们要确保写入数据和读取数据时的编码方式相同,在IDEA中,可在右下角查看使用的编码方式

在我们未学习InputStream之前,我们读取数据是通过Scanner来进行读取的。在上述例子中,对字符类型直接使用InputStream读取比较麻烦,因此,我们可以使用Scanner来进行读取

Scanner(InputStream inputStream, String charset)  //使用charset字符集对inputStream进行扫描读取

package IO;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class Demo7 {
    public static void main(String[] args) throws IOException {
        try (InputStream inputStream = new FileInputStream("./test.txt"){
            Scanner scanner = new Scanner(inputStream);
            //读文件
            while (scanner.hasNext()){
                String s = scanner.nextLine();
                System.out.println(s);
            }
        }
    }
}

在学习了文件读取操作后,我们来看写入数据操作

OutputStream

常用方法:

返回值类型方法说明
voidwrite(int b)写入数据b
voidwrite(byte[] b)将数组b中数据都写入
intwrite(byte[] b, int off, int len)将数组b中数据从off下标开始写入,一共写len个数据
voidclose()关闭字节流
voidflush()在进行写入数据时,为了减少设备操作的次数,会将数据先暂时写入内存中一个指定区域里,直到该区域满了或满足其他指定条件时才会将数据写到设备中,这个区域一般称为缓冲区。但我们写入的数据可能会遗留一部分在缓存区中,需要在最后或合适的位置,调用flush()(刷新)操作,将数据刷到设备中

OutputStream同样是一个抽象类,要使用需要具体的实现类。由于我们只关注将数据写入文件,因此使用FileOutputSteam

FileOutputStream

构造方法:

构造方法说明
FileOutputStream(File file)利用 File 构造文件输出流
FileOutputStream(String name)利用文件路径构造文件输出流

FileOutputStream的使用与FileInputStream的使用类似

package IO;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Demo8 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("./test.txt")){
            //一次写入一个字节数据
            outputStream.write(97);
            outputStream.write(98);
            outputStream.write(99);
            //将数组中所有数据都写入
            byte[] bytes = {100, 101, 102, 103, 104};
            outputStream.write(bytes);
            //写入数组中部分数据
            outputStream.write(bytes,1, 2);
            //将缓存区数据写到文件中
            outputStream.flush();
        }catch (IOException e){
            e.printStackTrace();
        }

    }
}

运行结果:在test.txt中,我们发现数据已经被写入

但上一次手动输入的“你好”被覆盖成新的数据,多运行几次代码,我们会发现:每次写入的数据都会覆盖掉旧的数据。OutputStream默认情况下,会将文件之前内容清空,然后重新写入数据(清空是打开操作引起的,而不是write)。若我们想接着上次的内容继续写,可以使用续写,在打开文件时打开续写开关,即可实现续写

OutputStream outputStream = new FileOutputStream("./test.txt", true)

以字节为单位,是否能够写入中文? 

package IO;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Demo8 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("./test.txt", true)){
            String s = "你好";
            byte[] b = s.getBytes("UTF-8");
            outputStream.write(b);
            //将缓存区数据写到文件中
            outputStream.flush();
        }catch (IOException e){
            e.printStackTrace();
        }

    }
}

字符流

在学习完字节流的相关操作后,此时再学习字符流操作就很简单了

字符流是一个字符一个字符的读,因此只能用来操作文本(不能写图片、音频、视频等)

Reader

常用方法:

返回值类型方法说明
intread()读取一个字符,当读取完毕,返回-1
intread(char[] cbuf)读取cbuf.length长度的字符到cbuf中,返回实际读取到的数量,当读取完毕,返回-1
intread(char[] cbuf, int off, int len)最多读取len大小的字符数据到cbuf中,从off下标开始存放,返回实际读取到的数量,当读取完毕,返回-1
intread(CharBuffer target)将字符读入到字符缓存区target中,返回实际添加到缓冲区的字符数,当读取完毕,返回-1
voidclose()关闭字符流

Rreader同样是一个抽象类,要使用需要具体的实现类。与文件中读取相关的类是 FileReader

FilerReader

构造方法:

package IO;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class Demo9 {
    public static void main(String[] args) {
        try (Reader reader = new FileReader("./test.txt")){
            while (true){
                char[] buffer = new char[1024];
                int n = reader.read(buffer);
                if(n == -1){
                    break;
                }
                String s = new String(buffer, 0, n);
                System.out.println(s);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

Writer

常用方法:

Writer与文件相关具体实现类:FileWriter

FileWriter

构造方法:

示例: 

package IO;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class Demo10 {
    public static void main(String[] args) {
        try(Writer writer = new FileWriter("./test.txt", true)){
            String s = "你好";
            writer.write(s);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

练习

在学习了文件基础知识和文件相关操作后,我们通过一些练习来进一步加深我们对知识的理解

练习1

扫描指定目录,找到名称中包含指定字符的所有普通文件

要在指定目录中查找包含指定字符的普通文件,首先我们要通过键盘输入指定字符和搜索目录,然后在指定目录中查找

代码实现:

package IO;

import java.io.File;
import java.util.Scanner;

public class Demo11 {
    public static void main(String[] args) {
        //输入指定信息
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要搜索的文件名关键字:");
        String name = scanner.next();
        System.out.println("请输入要搜索的目录:");
        String path = scanner.next();
        //判断输入的路径是否存在
        File pahtFile = new File(path);
        if(!pahtFile.isDirectory()){
            System.out.println("搜索目录有误!");
            return;
        }
        //在指定路径下查找目标文件
        //通过递归的方式来搜索目标文件
        scanDir(pahtFile,name);
    }


    private static void scanDir(File file, String filename){
        //列出当前目录下所有文件
        File[] files = file.listFiles();
        //若目录为空,直接返回
        if(files == null){
            return;
        }
        //若不为空,遍历目录下所有文件
        for (File f: files) {
            if(f.isFile()){//遍历到普通文件,判断是否为目标文件
                if(f.getName().contains(filename)){
                    System.out.println("符合目标文件:" + f.getName());
                }
            }else if(f.isDirectory()){//遍历到目录文件,继续递归
                scanDir(f, filename);
            }
        }
    }
}

练习2

普通文件的复制

要实现普通文件的复制,我们首先要获取复制的源文件路径和复制目标文件路径,并判断路径是否正确,然后再从源文件中读取数据并写入到目标文件中

代码实现:

package IO;

import java.io.*;
import java.util.Scanner;

public class Demo12 {
    public static void main(String[] args) {
        //输入指定信息
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要复制的源文件:");
        String src = scanner.next();
        System.out.println("请输入要复制的目标文件");
        String dest = scanner.next();
        //判断输入信息是否符合要求
        //源文件必须存在且是普通文件
        File srcFile = new File(src);
        if(!srcFile.isFile()){
            System.out.println("源文件有误!");
            return;
        }
        //目标文件可以存在也可以不存在,但其目录必须存在
        File destFile = new File(dest);
        if(!destFile.getParentFile().isDirectory()){
            System.out.println("目标路径有误!");
            return;
        }
        try (InputStream inputStream = new FileInputStream(srcFile);
             OutputStream outputStream = new FileOutputStream(destFile)){
            while (true){
                byte[] buffer = new byte[1024];
                //读取源文件中数据
                int n = inputStream.read(buffer);
                if(n == -1){//读取完毕
                    break;
                }
                //将数据写入到目标文件中
                outputStream.write(buffer,0,n);
            }

        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

楠枬

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

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

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

打赏作者

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

抵扣说明:

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

余额充值