天天看点

【java快速入门-IO篇】- File对象操作File对象的建立文件和目录创建和删除文件遍历文件和目录PathInputStreamOutputStream

File对象的建立

在计算机系统中,文件是非常重要的存储方式。Java的标准库

java.io

提供了

File

对象来操作文件和目录。

要构造一个

File

对象,需要传入文件路径:

public class Main {
    public static void main(String[] args) {
        File f = new File("C:\\Windows\\test.txt");
        System.out.println(f);
    }
}
           

构造File对象时,既可以传入绝对路径,也可以传入相对路径。绝对路径是以根目录开头的完整路径,例如:

File f = new File("C:\\Windows\\test.txt");
           

 传入相对路径时,相对路径前面加上当前目录就是绝对路径:

// 假设当前目录是C:\Docs
File f1 = new File("sub\\javac"); // 绝对路径是C:\Docs\sub\javac
File f3 = new File(".\\sub\\javac"); // 绝对路径是C:\Docs\sub\javac
File f3 = new File("..\\sub\\javac"); // 绝对路径是C:\sub\javac
           

可以用

.

表示当前目录,

..

表示上级目录。

获取FIle对象的路径的方法

1.

getPath()

,返回构造方法传入的路径

2.

getAbsolutePath()

,返回绝对路径

3.

getCanonicalPath

,它和绝对路径类似,但是返回的是规范路径

public class Main {
    public static void main(String[] args) throws IOException {
        File f = new File("..");
        System.out.println(f.getPath());
        System.out.println(f.getAbsolutePath());
        System.out.println(f.getCanonicalPath());
    }
}

..
/app/..
/
           

文件和目录

File

对象既可以表示文件,也可以表示目录。特别要注意的是,构造一个

File

对象,即使传入的文件或目录不存在,代码也不会出错,因为构造一个

File

对象,并不会导致任何磁盘操作。只有当我们调用

File

对象的某些方法的时候,才真正进行磁盘操作。

调用

isFile()

,判断该

File

对象是否是一个已存在的文件,调用

isDirectory()

,判断该

File

对象是否是一个已存在的目录:

public class Main {
    public static void main(String[] args) throws IOException {
        File f1 = new File("C:\\Windows");
        File f2 = new File("C:\\Windows\\test.txt");
        File f3 = new File("C:\\Windows\\nothing.txt");
        System.out.println(f1.isFile());
        System.out.println(f1.isDirectory());
        System.out.println(f2.isFile());
        System.out.println(f2.isDirectory());
        System.out.println(f3.isFile());
        System.out.println(f3.isDirectory());
    }
}

output:
false
false
false
false
false
false
           

判断文件的权限和大小:

  • boolean canRead()

    :是否可读;
  • boolean canWrite()

    :是否可写;
  • boolean canExecute()

    :是否可执行;
  • long length()

    :文件字节大小。

创建和删除文件

创建和删除文件

当File对象表示一个文件时,可以通过

createNewFile()

创建一个新文件,用

delete()

删除该文件:

File file = new File("test.1txt");
        if (file.createNewFile()) {
            // 文件创建成功:
            // TODO:
            System.out.println(file);
            if (file.delete()) {
                // 删除文件成功:
                System.out.println("delete suc");
            }
        }
           

 创建临时文件和删除临时文件

File对象提供了

createTempFile()

来创建一个临时文件,以及

deleteOnExit()

在JVM退出时自动删除该文件。

public class Main {
    public static void main(String[] args) throws IOException {
        File f = File.createTempFile("tmp-", ".txt"); // 提供临时文件的前缀和后缀
        f.deleteOnExit(); // JVM退出时自动删除
        System.out.println(f.isFile());
        System.out.println(f.getAbsolutePath());
    }
}
output:
true
C:\Users\lilin\AppData\Local\Temp\tmp-17119679714704568631.txt
           

遍历文件和目录

当File对象表示一个目录时,可以使用

list()

listFiles()

列出目录下的文件和子目录名。

listFiles()

提供了一系列重载方法,可以过滤不想要的文件和目录:

public class Main {
    public static void main(String[] args) throws IOException {
        File f = new File("C:\\Windows");
        File[] fs1 = f.listFiles(); // 列出所有文件和子目录
        String[] fs2 = f.list();//只列出文件名
        for (String string:fs2) {
            System.out.println("string="+string);
        }
        printFiles(fs1);
        File[] fs3 = f.listFiles(new FilenameFilter() { // 仅列出.exe文件
            public boolean accept(File dir, String name) {
                return name.endsWith(".exe"); // 返回true表示接受该文件
            }
        });
        printFiles(fs3);
    }

    static void printFiles(File[] files) {
        System.out.println("==========");
        if (files != null) {
            for (File f : files) {
                System.out.println(f);
            }
        }
        System.out.println("==========");
    }
}
           

和文件操作类似,File对象如果表示一个目录,可以通过以下方法创建和删除目录:

  • boolean mkdir()

    :创建当前File对象表示的目录;
  • boolean mkdirs()

    :创建当前File对象表示的目录,并在必要时将不存在的父目录也创建出来;
  • boolean delete()

    :删除当前File对象表示的目录,当前目录必须为空才能删除成功。

Path

public class Main {
    public static void main(String[] args) throws IOException {
        Path p1 = Paths.get(".", "project", "study"); // 构造一个Path对象
        System.out.println(p1);
        Path p2 = p1.toAbsolutePath(); // 转换为绝对路径
        System.out.println(p2);
        Path p3 = p2.normalize(); // 转换为规范路径
        System.out.println(p3);
        File f = p3.toFile(); // 转换为File对象
        System.out.println(f);
        for (Path p : Paths.get("..").toAbsolutePath()) { // 可以直接遍历Path
            System.out.println("  " + p);
        }
    }
}
           
.\project\study
D:\javaSE_IDEA\LearningJava\File\.\project\study
D:\javaSE_IDEA\LearningJava\File\project\study
D:\javaSE_IDEA\LearningJava\File\project\study
  javaSE_IDEA
  LearningJava
  File
  ..
           

InputStream

InputStream

就是Java标准库提供的最基本的输入流。它位于

java.io

这个包里。

java.io

包提供了所有同步IO的功能。

要特别注意的一点是,

InputStream

并不是一个接口,而是一个抽象类,它是所有输入流的超类。这个抽象类定义的一个最重要的方法就是

int read()

,签名如下:

public abstract int read() throws IOException;
           

这个方法会读取输入流的下一个字节,并返回字节表示的

int

值(0~255)。如果已读到末尾,返回

-1

表示不能继续读取了。

FileInputStream

就是从文件流中读取数据。例如

public void readFile() throws IOException {
    // 创建一个FileInputStream对象:
    InputStream input = new FileInputStream("src/readme.txt");
    for (;;) {
        int n = input.read(); // 反复调用read()方法,直到返回-1
        if (n == -1) {
            break;
        }
        System.out.println(n); // 打印byte的值
    }
    input.close(); // 关闭流
}
           

InputStream

OutputStream

都是通过

close()

方法来关闭流。关闭流就会释放对应的底层资源。

潜在的问题:如果读取过程中发生了IO错误,

InputStream

就没法正确地关闭,资源也就没法及时释放。

因此,我们需要用

try ... finally

来保证

InputStream

在无论是否发生IO错误的时候都能够正确地关闭:

public void readFile() throws IOException {
    InputStream input = null;
    try {
        input = new FileInputStream("src/readme.txt");
        int n;
        while ((n = input.read()) != -1) { // 利用while同时读取并判断
            System.out.println(n);
        }
    } finally {
        if (input != null) { input.close(); }
    }
}
           

try ... finally

来编写上述代码会感觉比较复杂,更好的写法是利用Java 7引入的新的

try(resource)

的语法,只需要编写

try

语句,让编译器自动为我们关闭资源。推荐的写法如下:

public void readFile() throws IOException {
    try (InputStream input = new FileInputStream("src/readme.txt")) {
        int n;
        while ((n = input.read()) != -1) {
            System.out.println(n);
        }
    } // 编译器在此自动为我们写入finally并调用close()
}
           

实际上,编译器并不会特别地为

InputStream

加上自动关闭。编译器只看

try(resource = ...)

中的对象是否实现了

java.lang.AutoCloseable

接口,如果实现了,就自动加上

finally

语句并调用

close()

方法。

InputStream

OutputStream

都实现了这个接口,因此,都可以用在

try(resource)

中。

缓冲

在读取流的时候,一次读取一个字节并不是最高效的方法。很多流支持一次性读取多个字节到缓冲区,对于文件和网络流来说,利用缓冲区一次性读取多个字节效率往往要高很多。

InputStream

提供了两个重载方法来支持读取多个字节:

  • int read(byte[] b)

    :读取若干字节并填充到

    byte[]

    数组,返回读取的字节数
  • int read(byte[] b, int off, int len)

    :指定

    byte[]

    数组的偏移量和最大填充数

利用上述方法一次读取多个字节时,需要先定义一个

byte[]

数组作为缓冲区,

read()

方法会尽可能多地读取字节到缓冲区, 但不会超过缓冲区的大小。

read()

方法的返回值不再是字节的

int

值,而是返回实际读取了多少个字节。如果返回

-1

,表示没有更多的数据了。

利用缓冲区一次读取多个字节的代码如下

public void readFile() throws IOException {
    try (InputStream input = new FileInputStream("src/readme.txt")) {
        // 定义1000个字节大小的缓冲区:
        byte[] buffer = new byte[1000];
        int n;
        while ((n = input.read(buffer)) != -1) { // 读取到缓冲区
            System.out.println("read " + n + " bytes.");
        }
    }
}
           

阻塞

在调用

InputStream

read()

方法读取数据时,我们说

read()

方法是阻塞(Blocking)的。它的意思是,对于下面的代码:

int n;
n = input.read(); // 必须等待read()方法返回才能执行下一行代码
int m = n;
           

执行到第二行代码时,必须等

read()

方法返回后才能继续。因为读取IO流相比执行普通代码,速度会慢很多,因此,无法确定

read()

方法调用到底要花费多长时间。

InputStream实现类

FileInputStream

ByteArrayInputStream

ByteArrayInputStream

可以在内存中模拟一个

InputStream,例如:

public class Main {
    public static void main(String[] args) throws IOException {
        byte[] data = { 72, 101, 108, 108, 111, 33 };
        try (InputStream input = new ByteArrayInputStream(data)) {
            int n;
            while ((n = input.read()) != -1) {
                System.out.println((char)n);
            }
        }
    }
}
           

ByteArrayInputStream

实际上是把一个

byte[]

数组在内存中变成一个

InputStream

,虽然实际应用不多,但测试的时候,可以用它来构造一个

InputStream

OutputStream

  和

InputStream

类似,

OutputStream

也是抽象类,它是所有输出流的超类。这个抽象类定义的一个最重要的方法就是

void write(int b)

,签名如下:

public abstract void write(int b) throws IOException;
           

这个方法会写入一个字节到输出流。要注意的是,虽然传入的是

int

参数,但只会写入一个字节,即只写入

int

最低8位表示字节的部分(相当于

b & 0xff

)。

InputStream

类似,

OutputStream

也提供了

close()

方法关闭输出流,以便释放系统资源。要特别注意:

OutputStream

还提供了一个

flush()

方法,它的目的是将缓冲区的内容真正输出到目的地。

通常情况下,我们不需要调用这个

flush()

方法,因为缓冲区写满了

OutputStream

会自动调用它,并且,在调用

close()

方法关闭

OutputStream

之前,也会自动调用

flush()

方法。

FileOutputStream

FileOutputStream

为例,演示如何将若干个字节写入文件流:

public void writeFile() throws IOException {
    OutputStream output = new FileOutputStream("out/readme.txt");
    output.write(72); // H
    output.write(101); // e
    output.write(108); // l
    output.write(108); // l
    output.write(111); // o
    output.close();
}
           

 每次写入一个字节非常麻烦,更常见的方法是一次性写入若干字节。这时,可以用

OutputStream

提供的重载方法

void write(byte[])

来实现:

public void writeFile() throws IOException {
    OutputStream output = new FileOutputStream("out/readme.txt");
    output.write("Hello".getBytes("UTF-8")); // Hello
    output.close();
}
           

InputStream

一样,上述代码没有考虑到在发生异常的情况下如何正确地关闭资源。写入过程也会经常发生IO错误,例如,磁盘已满,无权限写入等等。我们需要用

try(resource)

来保证

OutputStream

在无论是否发生IO错误的时候都能够正确地关闭:

public void writeFile() throws IOException {
    try (OutputStream output = new FileOutputStream("out/readme.txt")) {
        output.write("Hello".getBytes("UTF-8")); // Hello
    } // 编译器在此自动为我们写入finally并调用close()
}

同时操作多个AutoCloseable资源时,在try(resource) { ... }语句中可以同时写出多个资源,用;隔开。例如,同时读写两个文件:

// 读取input.txt,写入output.txt:
try (InputStream input = new FileInputStream("input.txt");
     OutputStream output = new FileOutputStream("output.txt"))
{
    
}
           

阻塞

InputStream

一样,

OutputStream

write()

方法也是阻塞的。

OutputStream实现类

同样除了FileOutputStream,还有一个

ByteArrayOutputStream

可以在内存中模拟一个

OutputStream。

public class Main {
    public static void main(String[] args) throws IOException {
        byte[] data;
        try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            output.write("Hello ".getBytes("UTF-8"));
            output.write("world!".getBytes("UTF-8"));
            data = output.toByteArray();
        }
        System.out.println(new String(data, "UTF-8"));
    }
}