Java IO 详细解析

本文详细介绍了Java IO中的File操作,包括文件和文件夹的创建、删除、重命名等。接着讲解了IO流的概念,如字节流、字符流、转换流和打印流的使用,特别强调了流的关闭和数据读写的注意事项。此外,还探讨了序列化和反序列化,以及在部分对象序列化时如何使用transient关键字。
摘要由CSDN通过智能技术生成

IO

File

FIle 是文件对象,可以表示一个文件,也可以表示文件夹。研究其源码,没有什么意义,我们要做的,是研究怎么用。

mkdir()

创建单个文件夹

mkdirs()

创建多个文件夹

一般都用这个方法,避免路径中出现多个文件夹不存在

createNewFile()

创建新的文件

delete()

删除文件或者文件夹

getAbsolutePath()

获取文件绝对路径

getName()

获取文件名

getParent()

获取父文件夹的绝对路径

getParentFile()

获取父文件夹

length()

获取文件大小,单位是字节

list()

获取当前文件夹下的所有子文件的绝对路径(只是往下一层)

listFiles()

获取当前文件夹下的所有子文件(只是往下一层)

renameTo(File newFile)

将原本的文件,重命名为另一个文件,如果另一个文件在其他文件夹,则将当前文件,转移到新的文件夹

———— 实战————

判断文件夹是否存在,不存在,则创建

public static void main(String[] args) throws IOException {
        String path = "/Users/faro_z/Documents/测试路径/testDir";
        File fold = new File(path);
        if (!fold.exists() && !fold.isDirectory()) {
            /**
             * mkdirs()的作用是,如果有多级目录不存在,就全部创建
             * 相应的,如果
             */
            fold.mkdirs();
            System.out.println("文件创建成功");
        } else {
            System.out.println("文件创建失败");
        }
    }

image-20210504141120248

判断文件是否存在,不存在,则创建

public static void main(String[] args) {
        /**
         * 给定路径结尾,需要是一个文件
         * 1. 如果路径错误,或其中的部分文件夹不存在,会报错
         * 2. 如果路径结尾的文件没有文件后缀,会创建一个空白文件
         */
        String path = "/Users/faro_z/Documents/测试路径/testDir/text.txt";
        File file = new File(path);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("文件已创建");
        } else {
            System.out.println("文件已存在");
        }
    }

image-20210504141751046

依据传来的文件夹,在里面新建文件

public static void main(String[] args) {

        /**
         * 创建文件夹
         */
        String foldPath = "/Users/faro_z/Documents/测试路径/testDir";
        File fold = new File(foldPath);
        if (!fold.exists() && !fold.isDirectory()) {
            fold.mkdirs();
            System.out.println("文件创建成功");
        } else {
            System.out.println("文件创建失败");
        }


        /**
         * 根据传来的文件夹,在里面新建文件
         */
        File file = new File(fold,"haha.txt");
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("文件已创建");
        } else {
            System.out.println("文件已存在");
        }
    }

image-20210504142407834

遍历一个文件夹下的所有文件

这个需求还是很容易的,只要使用递归就可以了

public class FileDemo2 {
    public static void main(String[] args) {
        String path = "/Users/faro_z/文件/坚果云/文档/MarkDown";
        File file = new File(path);
        printAllFile(file);
    }

    /**
     * 打印某一文件夹下的所有文件
     * 我这里,只打印以.md 结尾的文件
     * @param file
     */
    public static void printAllFile(File file) {
        //递归终止条件
        if (!file.isDirectory()) {
            String name = file.getName();
            //我只想打印以.md 结尾的文件
            if (name.endsWith(".md")) {
                System.out.println(name);
            }
            return;
        }
        File[] files = file.listFiles();
        if (files==null) return;
        if (files.length==0) return;
        for (File file1 : files) {
            printAllFile(file1);
        }
    }
}

image-20210504151126786

IO 流

IO流概述

IO流的分类:

按照流的方向:输入流和输出流

按照流的数据类型:字节流和字符流

下面介绍一下字节流字符流顶级父类

字节流:

  • 输入流:InputStream
  • 输出流:OutputStream

字符流:

  • 输入流:Reader
  • 输出流:Writer

所有的文件传输的单位,都是字节,就连字符流,其底层的传输单位,也是字节

————字节流————

OutputStream

close()

关闭流

流用完,一定要关闭

我们在编程的过程中,可能对关闭流不太上心,但是,想想我们生活中,一定遇到过这样的问题:
我们在用完某个程序,关闭它以后,想将其删除,却显示该程序正在执行,这可能是因为,这个程序中,有某个流在使用完后,没有关闭。我们编写代码,就应该极力避免这类问题。

flush()

强制将缓存中的数据写出

==注意:==在执行 close()的时候,程序会自动执行fluse()

如果我们既没有 flush,也没有 close,那么,文件就会一直在缓存中,不会写入外存

FlieOutputStream

这里,主要注意构造的时候,我们可以选择这个流是添加,还是覆写

image-20210504154218132

FileInputStream

读取时,注意读取的数据长度

我们要注意读取的数据长度,如果用read(byte[] b),会出现读取的问题,下面我来演示一下

我们的测试文件中,是 a-z 26 个字母:

image-20210504160421779

我们只使用read(byte[] b)读取文件:

public static void main(String[] args) throws IOException {
    String filePath = "/Users/faro_z/Documents/测试路径/testDir/haha.txt";
    FileInputStream fis = new FileInputStream(new File(filePath));
    byte[] bytes = new byte[10];

    fis.read(bytes);
    String str1 = new String(bytes);
    System.out.println(str1);

    fis.read(bytes);
    String str2 = new String(bytes);
    System.out.println(str2);

    fis.read(bytes);
    String str3 = new String(bytes);
    System.out.println(str3);
  
  	fis.close();
}

可以发现,最后的结果中,莫名其妙多出了 qrst

image-20210504160532321

这是因为,byte数组还存留上一次读取的数据,我们最后只剩下 6 个字节的新数据,那么,后面 4 个字节,就是上一次读取的老数据

为了避免这种情况,我们应该使用数据长度,去读取定长的数据:

public static void main(String[] args) throws IOException {
        String filePath = "/Users/faro_z/Documents/测试路径/testDir/haha.txt";
        FileInputStream fis = new FileInputStream(new File(filePath));
        byte[] bytes = new byte[10];

        int len = fis.read(bytes);
        String str1 = new String(bytes,0,len);
        System.out.println(str1);

        len = fis.read(bytes);
        String str2 = new String(bytes,0,len);
        System.out.println(str2);

        len = fis.read(bytes);
        String str3 = new String(bytes,0,len);
        System.out.println(str3);
  
			  fis.close();
    }

image-20210504161001074

怎么判断为文件读取完

当文件读取完后,再读的话,返回的是-1

这样,我们就得到了一般读字节流文件的形式:

public static void main(String[] args) throws IOException {
        String filePath = "/Users/faro_z/Documents/测试路径/testDir/haha.txt";
        FileInputStream fis = new FileInputStream(new File(filePath));
        byte[] bytes = new byte[10];

        int len=0;
        StringBuilder sb = new StringBuilder();
        while ((len=fis.read(bytes))!=-1) {
            sb.append(new String(bytes,0,len));
        }
        System.out.println(sb.toString());
    }

image-20210504161404981

————字符流————

FileReader

FileWriter

其实,操作和 FileOutputStream 差不多,我们就看代码:

下面这段代码,是使用追加的方式,为 古诗.txt 文件中,添加李白的古诗

public static void main(String[] args) throws IOException {
        String filePath = "/Users/faro_z/Documents/测试路径/testDir/古诗.txt";
        File file = new File(filePath);

        //使用的是追加方式
        FileWriter fw = new FileWriter(file, true);

        fw.append("\n床前明月光")
                .append("\n疑是地上霜")
                .append("\n举头望明月")
                .append("\n低头思故乡");

        fw.close();
    }

这里,我们建议使用==PrintWriter==

flush()

我们先看下面这段代码:

我们既没有 flush,也没有 close

public static void main(String[] args) throws IOException {        String filePath = "/Users/faro_z/Documents/测试路径/testDir/古诗2.txt";        File file = new File(filePath);        if (!file.exists()) {            System.out.println("文件不存在,创建文件");            file.createNewFile();        }        FileWriter fw = new FileWriter(file, true);        fw.append("\n床前明月光")                .append("\n疑是地上霜")                .append("\n举头望明月")                .append("\n低头思故乡");        //我们不 close        // fw.close();    }

这个时候,我们发现,内容根本没有写入外存:

image-20210504171054203

如果我们不close,但是执行 flush:

public static void main(String[] args) throws IOException {
        String filePath = "/Users/faro_z/Documents/测试路径/testDir/古诗2.txt";
        File file = new File(filePath);
        if (!file.exists()) {
            System.out.println("文件不存在,创建文件");
            file.createNewFile();
        }


        FileWriter fw = new FileWriter(file, true);

        fw.append("\n床前明月光")
                .append("\n疑是地上霜")
                .append("\n举头望明月")
                .append("\n低头思故乡");

        //我们不 close
        // fw.close();

        //但是我们 flush
        fw.flush();
    }

这个时候,可以发现,内容都被写入外存了:

image-20210504171322584

一般在通过 while 写大量内容的时候,我们每次最好都 flush一下,避免中途程序出现异常,导致之前在缓存中的内容丢失,没有写入外存。

————转换流————

为什么需要转换流?

在网络传输的时候,我们只能传输字节数据,不能传输字符数据,那如果我们还想对这些数据进行字符操作,该怎么办呢?这个时候,就轮到转换流登场了。

InputStreamReader

public static void main(String[] args) throws IOException {
        /**
         * 我们假装这是  从网络中   传来的字节数据
         * 但是我们相对其向字符一样进行操作
         */
        FileInputStream fis = new FileInputStream("/Users/faro_z/Documents/测试路径/testDir/古诗2.txt");
        /**
         * 我们通过转换流,将其转换为
         */
        InputStreamReader isr = new InputStreamReader(fis);
        char[] chars = new char[10];
        int len=0;
        StringBuilder sb = new StringBuilder();
        while ((len=isr.read(chars))!=-1) {
            sb.append(new String(chars,0,len));
        }
        System.out.println(sb.toString());
    }

OutputStreamWriter

其实,和 InputStreamReader 的操作大同小异

public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("/Users/faro_z/Documents/测试路径/testDir/古诗2.txt",true);
        /**
         * 我们通过转换流,将其转换为
         */
        OutputStreamWriter osw = new OutputStreamWriter(fos);
        osw.append("hahaha")
                .append("hehehe")
                .append("hihihi");

        osw.flush();
        osw.close();

    }

image-20210504173526959

————打印流————

PrintWriter(建议使用

将字节流,转换为打印流,我们就可以使用 println()方法,在写出的时候,换行。但还是要注意流关闭和 flush 的问题

这里,不建议使用 BufferedWriter

public static void main(String[] args) throws FileNotFoundException {
        FileOutputStream fos = new FileOutputStream("/Users/faro_z/Documents/测试路径/testDir/古诗2.txt",true);
        PrintWriter pw = new PrintWriter(fos);
        pw.println("早发白帝城");
        pw.println("朝辞白帝彩云间");
        pw.println("千里江陵一日还");

        //别忘了关闭流,不然,如果还没有 flush,那就不会写入外存
        pw.close();
    }

image-20210504174138832

————缓存流————

BufferedReader

BufferedReader,多了一个 readLine()方法,可以一次读取一行字符串

public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("/Users/faro_z/Documents/测试路径/testDir/古诗2.txt");
        InputStreamReader isr = new InputStreamReader(fis);
        /**
         * BufferedReader不能直接转换字节流
         * 要通过转换流转换一下
         */
        BufferedReader br = new BufferedReader(isr);

        String line;
        /**
         * 只有在读到文件末尾,才会返回 null
         * 如果文件中间有部分是空的,是不会返回 null 的
         */
        while ((line=br.readLine())!=null) {
            System.out.println(line);
        }
    }

image-20210504175003868

小结

这里,我将所有流的关系,做一个梳理

image-20210315152528221

image-20210315154310097

序列化和反序列化

**序列化:**将对象,存到文件里

**反序列化:**将存储到文件的对象,还原成对象

在执行序列化和反序列化之前,我们先定义一个 Book 类:

这里一定要注意,要为被序列化的内容,加上标记接口Serializable

/**
 * Serializable只是一个标记接口
 * 实现它不用实现任何方法
 */
class Book implements Serializable {
    private String name;
    private String desc;

    public Book(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public Book() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

序列化

使用 ObjectOutputStream,实现序列化:

public static void main(String[] args) throws IOException {
        //序列化
        Book book = new Book("金苹果", "讲述种植苹果的辛酸历程");
  			//我们要为 ObjectOutputStream 指定一个字节输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("book.txt"));
        oos.writeObject(book);
    }

最后序列化出的内容,是我们看不懂的:

image-20210504185523836

反序列化

public static void main(String[] args) throws IOException, ClassNotFoundException {
        // //序列化
        // Book book = new Book("金苹果", "讲述种植苹果的辛酸历程");
        // //我们要为 ObjectOutputStream 指定一个字节输出流
        // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("book.txt"));
        // oos.writeObject(book);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("book.txt"));
        Object o = ois.readObject();
        Book book=null;
        if (o instanceof Book) {
            book= (Book) o;
        }
        System.out.println(book);
    }

image-20210504185820072

注意

如果 Book 类中,还有一个属性是自定义的 Person 类:

image-20210504190033836

那如果想序列化 Book的话,Person 类也需要实现标记接口 Serializable:

image-20210504190134975

说白了,如果想要序列化某个对象,那么,其中包含的所有对象,都要可以被序列化!!!

部分对象序列化

当类实现 Serializable 接口的时候,是将整个类,都序列化的

但如果我们不想序列化其中的部分属性,我们有下面几种方式

  • 使用 transient 关键字
  • 将属性,声明为 static
  • 使用自定义的 readObject 和 writeObject
  • 使用Externalizable(需要实现 readObject 和 writeObject 方法)

这里,我就演示一下使用transient关键字,因为这个方法最常用:

Book 类:

Book 类和之前几乎一样

唯一不一样的地方,就是为 name 加上 transient 关键字

image-20210504191551668

我们再来执行一次序列化和反序列化的过程:

public static void main(String[] args) throws IOException, ClassNotFoundException {
        //序列化
        Book book = new Book("金苹果", "讲述种植苹果的辛酸历程");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("book.txt"));
        oos.writeObject(book);

        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("book.txt"));
        Object o = ois.readObject();
        Book book2=null;
        if (o instanceof Book) {
            book2= (Book) o;
        }
        System.out.println(book2);
    }

结果:

我们可以发现,name 是空的,说明,name 没有被序列化

image-20210504191641064

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

FARO_Z

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

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

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

打赏作者

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

抵扣说明:

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

余额充值