文件操作 和 IO - 详解

一,认识文件

1.1 树形结构组织和目录

文件是对于"硬盘"数据的一种抽象,在一台计算机上,有非常多的文件,这些文件是通过 "文件系统" 来进行组织的,本质上就是通过 "目录"(文件夹) 这样的树形结构来组织文件的,画个图理解一下:

有了目录,我们就可以使用目录的层次结构来描述文件所在的位置,即 "路径"。如:D:\Program Files (x86)\编程3\Common\VSPerfCollectionTools\vs2022\1033,在这里还有两个概念:

  • 绝对路径:以 C:D:盘符开头的,这种路径就是 "绝对路径"。
  • 相对路径:需要指定一个目录作为基准目录,从基准目录出发,到达指定的文件,这里的路径就是 "相对路径"。这些路径往往是以  . (代表当前目录) 或者  .. (代表当前目录的上一级目录) 开头的。

1.2 文件类型

文件主要分为两大类:

1)文本文件:文件中保存的数据都是字符串,保存的内容都是合法字符(计算机存储的数据都是二进制的,能通过字符编码将二进制数据转换成字符的就是合法字符)

2)二进制文件:文件中保存的数据是二进制数据,即不是合法的字符

区分文本文件和二进制文件:将文件直接使用记事本打开,如果是乱码,就是二进制文件,如果不是,就是文本文件。

二,文件操作 - FILE

2.1 属性

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

E:\01\MSDN 中的 \ 就是 pathSeparator,如果当前的系统是 Windows,\ 或者 / 都可以作为分隔符,如果系统是 Linux 或 Mac ,只能使用 / 作为分隔符,一般建议使用 / 作为分隔符,因为 \ 一般还需要搭配转义字符来使用。

2.2 构造方法

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

2.3 方法

返回值类型方法名说明
StringgetParent()
返回 File 对象的父目录文件路径
StringgetName()
返回 FIle 对象的纯文件名称
StringgetPath()
返回 File 对象的文件路径
StringgetAbsolutePath()
返回 File 对象的绝对路径
StringgetCanonicalPath()
返回 File 对象的修饰过的绝对路径
booleanexits()
判断 File 对象描述的文件是否真实存在
booleanisDirectory()
判断 File 对象代表的文件是否是一个目录
booleanisFile()
判断 File 对象代表的文件是否是一个普通文件
booleancreateNewFile()
根据 File 对象,自动创建一个空文件。成功创建后返
true
booleandelete()
根据 File 对象,删除该文件。成功删除后返回 true
voiddeleteOnExit()
根据 File 对象,标注文件将被删除,删除动作会到
JVM 运行结束时才会进行
String[]
list()
返回 File 对象代表的目录下的所有文件名
File[]
listFiles()
返回 File 对象代表的目录下的所有文件,以 File 对象表示
boolean
mkdir()
创建 File 对象代表的目录
boolean
mkdirs()创建 File 对象代表的目录,如果必要,会创建中间目录
boolean
renameTo(File dest)
进行文件改名,也可以视为我们平时的剪切、粘贴操
boolean
canRead()
判断用户是否对文件有可读权限
boolean
canWirte()
判断用户是否对文件有可写权限
public class Demo {
    public static void main(String[] args) throws IOException {
        File file = new File("./text.txt");//不要求该文件一定存在
        System.out.println(file.getParent());
        System.out.println(file.getName());
        System.out.println(file.getPath());
        System.out.println(file.getAbsolutePath());
        System.out.println(file.getCanonicalPath());
    }
}

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

public class Demo1 {
    public static void main(String[] args) throws IOException {
        File file = new File("d:/text.txt");
        System.out.println(file.exists());//false
        System.out.println(file.isDirectory());//false
        System.out.println(file.isFile());//false
        System.out.println(file.createNewFile());//true
        System.out.println(file.delete());//true
        //file.deleteOnExit();在程序全部执行完之后删除文件

        File file1 = new File("d:/");
        String[] ret = file1.list();
        System.out.println(Arrays.toString(ret));

        File file2 = new File("d:/aaa/bbb/ccc");
        boolean ans = file2.mkdirs();//能创建多级目录
        //file2.mkdir();只能创建一级目录,如 d:/aaa
        System.out.println(ans);
    }
}

三,文件内容读写 - 数据流

数据流根据文件类型也分成了两种:

1)字节流:对应二进制文件,每次读写的最小单位是 "字节"

2)字符流:对应文本文件,每次读写的最小单位是 "字符",英文的字符都是一个字节,一个汉字在不同的字符编码中是不同点大小,在 utf8 是 3 个字节,在 unicode 是 2 个字节。(字符流本质上是针对字节流进行的一层封装)

JAVA针对读写两种操作,分别为字节流提供了 InputStream(输入) 和 OutputStream(输出) 类,为字符流提供了 Reader(输入) 和 Writer(输出) 类。这里有一个注意点,如何区分输入和输出,画个图:

 3.1 字符流 - Reader

返回值类型方法名说明
intread()从文件中读取一个字符,返回unicode编码
intread(char[] cbuf)从文件中读取若干字符,将cbuf数组填满,返回实际读取的字符数
intread(chae[] cbuf, int off, int len)从文件中读取作干字符,从off下标开始,长度为len的cbuf数组填满,返回实际读取的字符数
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class Demo3 {
    public static void main(String[] args) throws IOException {
        //一次读一个字符
        Reader reader = new FileReader("d:/text.txt");//打开文件
        while(true){
            int n = reader.read();//读取一个字符
            if(n == -1){//返回-1表示文件读取完毕
                break;
            }
            char ch = (char) n;
            System.out.println(n);
        }
        reader.close();
    }
}

但是这么写还是可能会出现文件资源泄露,如果在while循环中抛出异常,下面的close()方法就执行不到了,所以我们可以使用 try...finally..来实现:

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

public class Demo3 {
    public static void main(String[] args) throws IOException {
        //一次读多个字符
        Reader reader = new FileReader("d:/text.txt");//打开文件
        try{
            while(true){
                char[] ret = new char[10];
                int n = reader.read(ret);
                if(n == -1) break;
                for (int i = 0; i < n; i++) {
                    System.out.println(ret[i]);
                }
            }
        }finally {
            reader.close();//关闭操作
        }
    }
}

这么写虽然解决了问题,但是不够方便,在这里还有一种写法:

public class Demo3 {
    public static void main(String[] args) throws IOException { 
        //只有实现closeable接口才可以这样写(流对象都可以)
        try(Reader reader = new FileReader("d:/text.txt")){
            while(true){
                char[] ret = new char[10];
                int n = reader.read(ret);
                if(n == -1) break;
                for (int i = 0; i < n; i++) {
                    System.out.println(ret[i]);
                }
            }
        }
    }
}

3.2 字符流 - Writer

方法名说明
write(int c)一次写一个字符
write(String str)一次写多个字符
write(char[] cbuf)一次写多个字符,使用字符数组
write(String str, int off, int len)从下标off开始往文件中写入,长度为len
write(char[] cbuf, int off, int len)从下标off开始往文件中写入,长度为len

注:默认情况下,写入文件会将文件中的原有内容清空。

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

public class Demo4 {
    public static void main(String[] args) {
        try(Writer writer = new FileWriter("d:/text.txt")) {
            writer.write("原神,启动!");//写入,先清空再写入
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    /*  在构造方法参数中加一个 true , 就可以直接在文件后面填写,不需要清空
        try(Writer writer1 = new FileWriter("d:/text.txt",true)) {
            writer1.write("原神,启动!");//写入
        } catch (IOException e) {
            throw new RuntimeException(e);
        }*/
    }
}

3.3 字节流 - InputStream

返回值类型方法名说明
intread()
读取一个字节的数据,返回 -1 代表已经完全读完了
intread(byte[] b)
最多读取 b.length 字节的数据到 b 中,返回实际读到的数量;-1 代表以及读完了
int
read(byte[] b, int off, int len)
最多读取 len - off 字节的数据到 b 中,放在从 off 开始,返回实际读到的数量;-1 代表以及读完了
void
close()
关闭字节流
import java.io.*;

public class Demo5 {
    public static void main(String[] args) {
        try(InputStream inputStream = new FileInputStream("d:/text.txt")) {
            byte[] buffer = new byte[10];
            while (true){
                int n = inputStream.read(buffer);
                if(n == -1) break;
                for (int i = 0; i < n; i++) {
                    System.out.printf("%x\n",buffer[i]);
                }
            }
        }catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

3.4 字节流 - OutputStream

返回值类型方法名说明
voidwrite()
写入要给字节的数据
voidwrite(byte[] b)
b 这个字符数组中的数据全部写入  
int
write (byte[] b, int off, int len)
b 这个字符数组中从 off 开始的数据写入 ,一共写 len
void
close()
关闭字节流
voidflush()
大多的 OutputStream 为 了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的 一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写 入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的 数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置, 调用 flush (刷新操作,将数据刷到设备中。
import java.io.*;

public class Demo5 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("d:/text.txt",true)){
            String s = "哈哈哈哈";
            outputStream.write(s.getBytes());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

3.5 字节流转字符流

当别人传给你的是一个字节流文件,但是你知道实际数据内容是文本数据时,我们可以通过以下方法来实现转换:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class Demo6 {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("d:/text.txt")){
            Scanner scanner = new Scanner(inputStream);
            String s = scanner.next();
            System.out.println(s);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

 

import java.io.*;

public class Demo7 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("d:/text.txt")){
            PrintWriter writer = new PrintWriter(outputStream);
            writer.println("fsaf");  
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

 因为 PrintWriter 这个类,在进行写入操作的时候,不一定时直接写入硬盘,而是先把数据写入一个内存中的空间,叫做 "缓冲区"。为什么会出现缓冲区?因为把数据写入内存,是非常快的,而把数据写入硬盘,是非常慢的(比内存慢几千倍甚至更多),为了提高效率,我们选择降低写硬盘的次数。这样就会出现问题,我们将数据写入 "缓冲区" 后,还没有将缓冲区的数据写入硬盘,进程就结束了,此时数据就丢失了,也就会出现上述图片中的问题

为了解决该问题,确保数据能完整的写入硬盘,我们需要手动的用 flush() 方法刷新缓冲区:

import java.io.*;

public class Demo7 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("d:/text.txt")){
            PrintWriter writer = new PrintWriter(outputStream);
            writer.println("fsaf");
            writer.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一叶祇秋

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

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

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

打赏作者

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

抵扣说明:

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

余额充值