Java中,操作文件的类有很多, 核心的部分是File类,InputStream,OutputStream类

File类

我们先来看看 File类中的常见属性、构造方法和方法

属性

修饰符及类型属性说明
static StringpathSeparator依赖于系统的路径分隔符,String 类型的表示
static charpathSeparator依赖于系统的路径分隔符,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 对象的绝对路径
StringgetCanonicalPath()返回 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()判断用户是否对文件有可写权限

注意,File类并不是给一个路径得到的就一定是文件 ,还有可能是一个目录!因此存在isDirectory() / isFile()方法来进行判断是否是目录/ 文件.

举一些用例:

import java.io.File;
import java.io.IOException;
public class Demo1 {
    public static void main(String[] args) throws IOException {
        //绝对路径
        File f = new File("e:/test.txt");
        //获取文件父目录
        System.out.println(f.getParent());  //e:\
        //获取到文件名
        System.out.println(f.getName()); //test.txt
        //获取到文件路径
        System.out.println(f.getPath()); //e:\test.txt
        //获取到绝对路径
        System.out.println(f.getAbsolutePath()); //e:\test.txt
        //获取绝对路径 
        System.out.println(f.getCanonicalPath()); // E:\test.txt
        System.out.println("==============");
        //构造一个相对路径文件
        File f2 = new File("./test.txt");
        //获取文件父目录
        System.out.println(f2.getParent()); // .
        //获取到文件名
        System.out.println(f2.getName()); // test.txt
        //获取到文件路径
        System.out.println(f2.getPath()); // .\test.txt
        //获取到绝对路径
        System.out.println(f2.getAbsolutePath()); // D:\HaoXiangsWareHouse\JavaCode\blogProject\.\test.txt
        //获取绝对路径
        System.out.println(f2.getCanonicalPath()); // D:\HaoXiangsWareHouse\JavaCode\blogProject\test.txt
    }
}

运行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PRze2hr6-1681816516779)(assets/image-20230418094535369.png)]

有两个点需要注意

  • 指定一个File的pathName的时候 即使该路径并没有所在的文件 / 目录, 仍然可以指定

上述Demo中pathName 是 “e:/test.txt” , 但是电脑中并没有 test.txt 文件,但是仍然是能够指定的,因此如果需要判断该路径是否有效,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eaPiFBRw-1681816516780)(assets/image-20230418094723202.png)]

因此需要使用exists() 之后在判断 isDirectory() / isFile() .

public static void main(String[] args) {
    File f2 = new File("e:/test.txt");
    File f = new File("./test.txt");
    System.out.println(f.exists());
    System.out.println(f.isDirectory());
    System.out.println(f.isFile());
}

运行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VUrxLkhv-1681816516782)(assets/image-20230418095855844.png)]

  • 还有一个注意的点就是当我们写相对路径的时候, 需要搞清楚当前目录位置在哪里,这里的相对目录位置就是项目所在目录即

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4G5Dwmli-1681816516782)(assets/image-20230418100130250.png)]

    此处我建立一个 test.txt文件,这样就可以正确的检测出是文件还是目录了.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wwkljop8-1681816516783)(assets/image-20230418100413389.png)]

    运行结果如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VzesDDNQ-1681816516783)(assets/image-20230418100552385.png)]

有了两点注意事项之后, 就可以通过相对路径来进行创建文件操作了.

通过File类创建文件

public class Demo3 {
    public static void main(String[] args) throws IOException {
        File f = new File("./test.txt");
        System.out.println(f.exists());
        System.out.println("创建文件");
        //成功会返回一个Boolean类型
        System.out.println(f.createNewFile());
        System.out.println("创建文件结束");
        System.out.println(f.exists());
    }
}

文件当前目录结构如下: (不存在 test.txt文件)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bUabAfL4-1681816516784)(assets/image-20230418100830024.png)]

运行结果如下 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WQlcRnGm-1681816516784)(assets/image-20230418101006724.png)]

再看目录结构, 就发现 test.txt文件已经被创建好了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Votmf19F-1681816516784)(assets/image-20230418101054244.png)]

通过File类创建目录

  • 创建单级目录 mkdir(); 该方法会在指定目录位置创建一个目录(文件夹)

    但是如果中间目录不存在那么就会创建失败

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1WV20bAn-1681816516784)(assets/image-20230418115753093.png)]

    当前项目位置并不存在 aaa/bbb/ccc目录,此时不创建中间目录 aaa/bbb 查看**mkdir()**方法是否会成功

    public static void main(String[] args) {
        File f = new File("./aaa/bbb/ccc");
        System.out.println(f.exists());
        System.out.println(f.mkdir());
        System.out.println(f.isDirectory());
        System.out.println(f.exists());
    }
    

    运行结果如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RHaPVTSK-1681816516785)(assets/image-20230418120148631.png)]

    创建中间目录aaa/bbb后:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AMUVsunH-1681816516785)(assets/image-20230418120301018.png)]

    可以看到目录ccc创建成功了

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ElPPI1UZ-1681816516785)(assets/image-20230418120403955.png)]

  • 创建多级目录 mkdirs() 如果中间目录不存在就会直接把中间目录全部创建

JavaEE目录下是没有aaa/bbb/ccc目录的, 观察是否会被创建成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BQRGfxJi-1681816516785)(assets/image-20230418120509113.png)]

import java.io.File;
public class Demo5 {
    public static void main(String[] args) {
        File f = new File("./aaa/bbb/ccc");
        System.out.println(f.exists());
        System.out.println(f.mkdirs());
        System.out.println(f.isDirectory());
        System.out.println(f.exists());
    }
}

运行结果如下 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0WHG0XKQ-1681816516786)(assets/image-20230418120554537.png)]

显而易见,目录之前不存在, 后来通过 mkdirs()方法 创建了多级目录之后,目录就已经存在啦.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qBTEsAcm-1681816516786)(assets/image-20230418121237152.png)]

文件内容的读写 – 数据流

InputStream

InputStream源码如下:

public abstract class InputStream implements Closeable {

    // MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to
    // use when skipping.
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;
    
    public abstract int read() throws IOException;

   
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

   
    public long skip(long n) throws IOException {

        long remaining = n;
        int nr;

        if (n <= 0) {
            return 0;
        }

        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;
            }
            remaining -= nr;
        }

        return n - remaining;
    }

    
    public int available() throws IOException {
        return 0;
    }

    
    public void close() throws IOException {}

    
    public synchronized void mark(int readlimit) {}

    
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

   
    public boolean markSupported() {
        return false;
    }

}

方法

InputStream提供了几个关键方法:

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

最常用的就是 read(byte[]b) 方法来进行读取文件内容

但是因为 InputStream是一个抽象类, 因此需要创建它的实现子类来进行文件读写,这里只关心文件读写, 因此使用FileInputStream

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O7BOGhfh-1681816516786)(assets/image-20230418122033763.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5FXImber-1681816516787)(assets/image-20230418122423035.png)]

FileInputStream的两个构造方法

public FileInputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null);
    }

    public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        fd = new FileDescriptor();
        fd.attach(this);
        path = name;
        open(name);
    }
签名说明
FileInputStream(File file)利用 File 构造文件输入流
FileInputStream(String name)利用文件路径构造文件输入流

案例: 假设在项目内创建一个test.txt文件, 文件内容是 hello World !

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GW2fmQVF-1681816516787)(assets/image-20230418123907052.png)]

  • 一次读取一个字符的方法
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class Demo2 {
    public static void main(String[] args) {
        // 用try语句包裹住,这样就可以避免路径非法之类的错误出现
        try(InputStream inputStream = new FileInputStream("./src/file/test.txt")) {
            //1. 一个一个字符读取
            StringBuilder stringBuilder = new StringBuilder();
            while (true) {
                int b = inputStream.read();
                // 说明已经读取完毕
                if (b == -1) {
                    break;
                }
                stringBuilder.append((char) b);
            }
            System.out.println(stringBuilder);
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3VmNMSsB-1681816516787)(assets/image-20230418123809039.png)]


  • 一次读取多个字符:

    可以通过定义一个buffer缓冲区,来进行缓冲字符,当缓冲区满了之后,就返回.

    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    public class Demo2 {
        public static void main(String[] args) {
            // 用try语句包裹住,这样就可以避免路径非法之类的错误出现
            try(InputStream inputStream = new FileInputStream("./src/file/test.txt")) {
                String tmp  = null;
                StringBuilder stringBuilder = new StringBuilder();
                while (true) {
                    byte [] buffer = new byte[1024];
                    int len = inputStream.read(buffer);
                    if(len == -1) {
                        break;
                    }
                    tmp = new String(buffer,0,len);
                    stringBuilder.append(tmp);
                }
                System.out.println(stringBuilder);
            }catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n1bVeSgv-1681816516788)(assets/image-20230418123913703.png)]
运行结果如下
在这里插入图片描述

  • 通过Scanner来进行字符读取

上述例子中,我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以,我们使用一种我们之前比较熟悉的类来完成该工作,就是 Scanner 类。

构造方法说明
Scanner(InputStream is, String charset)使用 charset 字符集进行 is 的扫描读取
// scanner读取Input内容
try(InputStream inputStream = new FileInputStream("./src/file/test.txt")) {
    // 指定输入流 和 字符集
    Scanner scanner = new Scanner(inputStream,"utf-8");
    while (true) {
        if (!scanner.hasNextLine()) {
            break;
        }
        String s = scanner.nextLine();
        System.out.println(s);
    }
}catch (IOException e) {
    e.printStackTrace();
}

运行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6rm5Smpt-1681816516788)(assets/image-20230418160451994.png)]

OutputStream

源码如下:

package java.io;

public abstract class OutputStream implements Closeable, Flushable {

    public abstract void write(int b) throws IOException;

    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

    public void write(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }

    public void flush() throws IOException {
    }

    public void close() throws IOException {
    }

}

方法

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

说明:

OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中, 所以使用 FileOutputStream

案例, 在之前的test.txt内添加 Hello Java! 字段

  • 每次写一个字符

    没有执行前test.txt文件内容:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a4t24q4O-1681816516788)(assets/image-20230418164000389.png)]

    try(OutputStream outputStream = new FileOutputStream("./src/file/test.txt")) {
        outputStream.write('h');
        outputStream.write('e');
        outputStream.write('l');
        outputStream.write('l');
        outputStream.write('o');
        outputStream.write(' ');
        outputStream.write('j');
        outputStream.write('a');
        outputStream.write('v');
        outputStream.write('a');
        outputStream.write('!');
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }	
    

此时打开test.txt文件查看内容:
在这里插入图片描述

我们发现之前的内容不见了! 因此一定注意,在FileOutputStream内,存在两个构造方法的参数

签名说明
FileOutputStream(File file)创建文件输出流以写入由指定的File对象表示的文件。
[FileOutputStream](https://docs.oracle.com/javase/7/docs/api/java/io/FileOutputStream.html#FileOutputStream(java.io.File, boolean))(File file, boolean append)创建文件输出流以写入由指定的File对象表示的文件。
FileOutputStream(String name)创建文件输出流以指定的名称写入文件。
[FileOutputStream](https://docs.oracle.com/javase/7/docs/api/java/io/FileOutputStream.html#FileOutputStream(java.lang.String, boolean))(String name, boolean append)创建文件输出流以指定的名称写入文件。

参数:

File file: 指定输出流文件

String name :指定输出流文件所在位置

那么 boolean append 的意义是什么呢 , 追溯源码 可以看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tAAFnZPw-1681816516789)(assets/image-20230418165655283.png)]

原来默认append 参数都是false , 也就是如果要对文件输出,那么就是从开头开始, 但是如果添加了 append 参数 , 那么文件就会从末尾开始, 而不是从开头,也就是对文件进行追加内容.

因此 , 我们只需要在代码构造FileOutpurStream的时候 指定append 参数为true即可实现追加内容的效果:

try(OutputStream outputStream = new FileOutputStream("./src/file/test.txt",true)) {
    ...
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RSjXUZUA-1681816516789)(assets/image-20230418165952080.png)]

  • 一个写多个字符

    同理, 只需要创建一个缓冲区即可:

    try(OutputStream outputStream = new FileOutputStream("./src/file/test.txt",true)) {
                byte [] buffer = new byte[]{'h','e','l','l','o',' ','j','a','v','a'};
                outputStream.write(buffer);
    } catch (IOException e) {
        ....
    }
    

    结果如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YH2qFtx3-1681816516789)(assets/image-20230418170434232.png)]

    • 写入中文

      try(OutputStream outputStream = new FileOutputStream("./src/file/test.txt",true)) {
              String s = "你好Java!";
              byte [] buffer = s.getBytes("utf8"); // 指定编码集
              outputStream.write(buffer);
          } catch (FileNotFoundException e) {
              throw new RuntimeException(e);
          } catch (IOException e) {
              throw new RuntimeException(e);
          }
      

      运行结果如下:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-axBiltuX-1681816516790)(assets/image-20230418170720958.png)]

    如此发现,使用FileOutputStream来进行写入操作有些繁琐,因为每次都需要一个一个字节指定,有没有更简单的方法呢?

肯定是有的,这里就可以使用PrinterWriter 来进行包裹流 来进行输出.

PrintWriter 类中提供了我们熟悉的 print/println/printf 方法.

try(OutputStream outputStream = new FileOutputStream("./src/file/test.txt")) {
    PrintWriter printWriter = new PrintWriter(outputStream);
    printWriter.println("HelloJava!!");
    printWriter.printf("hello Java!\n");
    printWriter.print("Hello,Java~");
    //这里一定要手动刷新缓冲区!, 因为缓冲区没有满printWritter不会输出到文件
    printWriter.flush();
} catch (FileNotFoundException e) {
    throw new RuntimeException(e);
} catch (IOException e) {
    throw new RuntimeException(e);
}

运行结果 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U013WMq2-1681816516790)(assets/image-20230418184401748.png)]

注意,这里使用printWriter的时候,一定要手动刷新缓冲区


.flush缓冲区

缓冲区存在的意义就是为了提高效率,

在计算机中尤其重要,CPU读取内存的速度大大高于硬盘,需要写数据到硬盘上,与其一次写一点,分多次写, 不如把一些数据攒一堆(这一堆数据在内存中保存的,这块内存就叫做缓冲区) 统一一次写完.

读操作也是类似, 与其一次读一点,分多次读,不如一次性读一堆数据, 再慢慢消化.

例如: 写数据的时候,需要把数据写到缓冲区里面, 然后再统一写在硬盘.

如果当前缓冲区已经写满了, 就直接出发写硬盘操作,

但是如果当前缓冲区还没满 ,也想提前写硬盘, 此时就可以通过flush来手动"刷新缓冲区";


Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐