java文件操作和IO

目录

🐳今日良言:总要尝遍所有的路,再对生活充满期待。

🐇一、认识文件

🐉二、操作文件

🐯三、练习



🐳今日良言:总要尝遍所有的路,再对生活充满期待。

🐇一、认识文件

1.认识文件

首先,认识一下文件,文件File这个概念,在计算机里面也是"一词多用".

狭义的文件:指的是我们磁盘上的文件和目录(文件夹)

广义的文件:泛指计算机中的很多软硬件资源.

 操作系统中把很多的硬件设备和软件资源都抽象成了文件,按照文件的方式来统一管理.这一点在网络编程中很明显,操作系统将网卡当成了一个文件

 本篇博客主要介绍的是狭义的文件.

2.两种路径

每个文件,在硬盘上都有一个具体的"路径"

上述路径叫做  d:/bin

表示一个文件的具体位置路径,就可以使用 / 来分割不同的目录级别.

 d:  c:  e:  这些叫做盘符,cde这样的盘符是通过"磁盘分区"来的,每个盘符可以是一个单独的硬盘,也可以是若干个盘符对应一个硬盘.

那么,试想一下:为什么没有 a: b: 呢?

其实是有的,A,B盘是软盘,A、B两个盘符都是系统分配给软驱的,存储空间很小,只有几个MB

通过上述简单的介绍,对于路径有了一定的了解,接下来介绍两种表示路径的风格:

1).绝对路径:以c: d: 盘符开头的路径

 

2).相对路径:以当前所在的目录为基准,以 . 或者 .. 开头(有时候可以省略),找到指定的路径

     这里当前所在目录称为工作目录,每个程序运行的时候,都有一个工作目录.

     假定当前的工作目录是 d:/106,定位到aaa这个目录就可以表示成: ./aaa

 ./就表示当前的目录

      如果工作目录不同,定位到同一个文件,相对路径写法是不同的.

      同样是定位到aaa这里:

      如果工作目录是d:   相对路径就写作 ./106/aaa

      如果工作目录是d:/106 相对路径就写作 ./aaa

      如果工作目录是d:/106/bbb 相对路径就写作 ../aaa (..表示当前目录的上级目录)

      如果工作目录是d:/106/bbb/ccc 相对路径就写作../../aaa

3.文件的类型

我们接触到的文件有哪些呢?

word  exe  图片 视频 音频 源代码...  这些都是不同的文件,但是整体可以归纳到两类中:

1).文本文件(存的是字符串,文本)

     字符串,是由字符构成的.每个字符都是通过一个数字来表示的,每个文本文件里面存的数据都是合法的字符.都是在指定字符编码的码表之内的数据.

2).二进制文件(存的是二进制数据,不一定是字符串)

    没有任何限制,可以存储任何想要存储的数据

通过上面的介绍,好像还是没法区分文本文件和二进制文件,那么,平常我们该如何区分这两类文件呢?

其实很简单,当随便拿到一个文件以后,直接使用记事本打开,如果看得懂里面内容,就说明是文本文件,如果看不懂,是乱码的情况下,就是二进制文件

🐉二、操作文件

在通过上面的介绍,对于文件有了初步认识,也了解到了路径,以及两类文件,接下来,详细介绍如何操作文件

Java对于文件的操作主要有:

1).针对文件系统操作(文件的创建 删除 重命名...)

2).针对文件内容操作(文件的读和写)

Java标准库提供了一个File类,通过这个File类来对文件(包括目录)进行抽象的描述,

注:有File对象,并不代表真实存在该文件.

1.File类的构造方法

方法说明
File(File parent,String child)

根据父目录+孩子文件路径,创建一个

新的File实例

File(String pathname)        

根据文件路径创建一个新的File实例,

路径可以是相对路径或者绝对路径        

File(String parent,String child)

根据父目录+孩子文件路径,创建一个

新的File实例,父目录用路径表示

:parent表示当前文件所在的目录  child  是自身的文件名.

    在new File对象的时候,构造方法参数中,可以指定一个路径(绝对路径或者相对路径都可以),此时,File对象就代表这个路径对应的文件了.

2.File类的相关方法

 通过下面代码以及运行结果来观察上面的方法:

public class IODemo1 {
    public static void main(String[] args) throws IOException {
        // 不要求在d: 这里真的有个text.txt
        File file = new File("d:/106/aaa/text.txt");//绝对路径
        //File  file = new File("./text.txt");// 相对路径
        System.out.println(file.getName());  // file的纯文件名称
        System.out.println(file.getParent());// file的父目录文件名称
        System.out.println(file.getPath()); // file的文件路径
        System.out.println(file.getAbsolutePath()); // file的绝对文件路径
        System.out.println(file.getCanonicalFile());// 修饰过的绝对路径
    }
}

运行结果

public class IODemo2 {
    public static void main(String[] args) throws IOException {
        File file = new File("./text.txt");
        file.createNewFile();// 创建一个文件
        System.out.println(file.exists()); // 是否存在
        System.out.println(file.isFile()); // 是不是文件
        System.out.println(file.isDirectory()); // 是不是目录
        // mkdir 只能创建一级目录
        // mkdirs 创建多级目录
        file.mkdirs()
    }
}

 运行结果

 剩下的方法,UU们可以自己试一下.

3.文件内容的读写

  针对文件内容,使用"流对象" 进行操作.

  "流对象"是一个形象的比喻,意思是,针对文件内容,要读100个字节,一次可以读一个字节,共读100次,也可以一次读10个字节,读10次,也可以一次读100个字节,读1次,这取决于自己,像水流一样生生不息.

   将数据视为池中的水:

   写操作:视为通过水龙头灌水---输出流

   读操作:视为通过水龙头接水---输入流

Java标准库的流对象从类型上分为成两个大类:

 1).字节流  操作二进制数据的

   InputStream  读    FileInputStream

   OutputStream 写   FileOutputStream

   

 2).字符流  操作文本文件的

     Reader  读   FileReader

     Writer    写   FileWriter

     这里涉及的类虽然很多,但是规律性很强,核心就是四个操作:

      1.打开文件.(构造对象)

      2.关闭文件.(close)

      3.读文件(read)    针对InputSream / Reader

      4.写文件(write)    针对OutputStream / Writer

通过下面代码实例来加深记忆和理解:

 1).通过字节流来读取文件:

public class IODemo6 {
    public static void main(String[] args) throws IOException {
        // 文件必须保证存在
        // 创建对象的时候,  使用绝对路径或者相对路径都是可以的 也可以使用File对象
        InputStream inputStream = new FileInputStream("d:/text.txt");
        // 进行读操作
        while (true) {
            int b = inputStream.read();
            if (b == -1) {
                break;
            }
            System.out.printf("%x\n",(byte)b);
        }
        // 关闭文件
        inputStream.close();
    }
}

 运行结果

先介绍一下inputstream的read方法

read 无参数版本:一次读一个字节

read一个参数版本:把读到的内容填充到参数的这个字节数组中(此处的参数是个"输出型参数")

                               返回值是实际读取的字节数

read三个参数版本:和一个参数的版本类似,只不过是往数组的一部分区间里尽可能填充.

read方法的返回值为什么要使用int呢?

 这是因为:read读取的是一个字节,按理说,返回一个byte就行了,但是实际上返回的是int,

除了要表示byte里的 0->255 (-128 ->127) 这样的情况之外,还需要表示一个特殊情况 -1

读到 -1 就表示读取文件结束了(读到文件末尾了)

为了提高IO效率,使用read一个参数版本,传入一个字节数组

public class IODemo6 {
    public static void main(String[] args) throws IOException {
        // 文件必须保证存在
        // 创建对象的时候,  使用绝对路径或者相对路径都是可以的 也可以使用File对象
        InputStream inputStream = new FileInputStream("d:/text.txt");
        while (true) {
            // buffe: 缓冲区  存在的意义就是提高IO操作的效率
            byte[] buffer = new byte[1024];
            // read 返回当前实际读取到的长度
            // 第二轮读到的数据会覆盖第一轮读到的数据
            // 所以说 读到数据就需要进行处理
            int len = inputStream.read(buffer);
            System.out.println("len:"+len);
            // 等于 -1就结束
            if (len == -1) {
                break;
            }
            // 此时读取的结果就放到了 byte数组了
            for (int i = 0; i < len; i++) {
                System.out.printf("%x\n",buffer[i]);
            }
        }
        // 关闭文件
        inputStream.close();
    }
}

运行结果:

 注意理解这里的read,read会尽可能的把参数传进来的数组给填满,在上述代码创建的字节数组长度是1024,read就会尽可能的读取1024个字节填到数组中,但是实际上,文件剩余长度是有限的,如果剩余长度超过1024,此时1024个字节都会被填满,返回值就是1024了,如果当前剩余长度不足1024,此时有多少就填多少,read方法就会返回当前实际读取的长度.

2).使用字节流写文件

public class IODemo7 {
    public static void main(String[] args) {
        // 对于outputStream来说.默认情况下,打开一个文件 会先清空这个文件原有的内容
        // 如果不想清空,流对象还提供了一个"追加写"对象 通过这个就可以实现不清空内容
        // 把新内容追加写到后面
        try (OutputStream outputStream = new FileOutputStream("d:/text.txt")) {
            outputStream.write(97);
            outputStream.write(98);
            outputStream.write(99);
            outputStream.write(100);
        }  catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

此时当运行以后,打开这个文件,观察里面内容:

 可以观察到,里面的内容确实是想要的结果.

接下来,详细解释上述代码涉及到的知识点:

在上述代码中,并没有使用close方法,close一般来说都是要执行的,如果不执行会怎么样?

进程在内核里,使用PCB这样的数据结构表示,PCB中有一个重要属性:文件描述符表.(相当于一个数组)记录了该进程打开了哪些文件.

每次打开文件操作,都会在文件描述符表中,申请一个位置,把这个信息放进去.

每次关闭文件操作,就会把文件描述符表对应的表项给释放掉

如果没有close,没有及时释放.虽然Java有GC  GC操作会在回收这个OutputStream对象的时候去完成这个释放操作,但是GC不一定释放及时.

所以,如果不手动释放, 意味着文件描述符表可能很快就会被占满了(这个数组,不能自动扩容,存在上限) ,如果占满之后,后面再次打开文件,就会打开失败.

文件描述符表最大长度基本上就是几百到几千左右..

所以说,在代码中,必须保证这个close被执行到,那么,如何才能保证这个close被执行到呢?

最推荐的写法就是这个写法:

这个写法虽然没有显式的写close,但是实际上是会执行的,只要try语句块执行完毕,就会自动执行close操作,这个语法,在Java中叫做:try with resource 

不是说随便一个对象都可以放到try的括号中,只有实现了Closeable接口的类才可以放到括号中被自动关闭.这个接口提供的方法就是close方法.

观察OutputStream类的源码会发现实现了这个Closeable接口:

如果try()里面定义的对象多于一个,使用分号 (;) 分割

 3).使用字符流读文件

public class IODemo8 {
    public static void main(String[] args) throws IOException {
        try (Reader reader = new FileReader("d:/text.txt")) {
            while (true) {
                int ch = reader.read();
                if (ch == -1) {
                    break;
                }
                System.out.println(" "+(char)ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果

 4).使用字符流写文件

public class IODemo9 {
    public static void main(String[] args) {
        try (Writer writer = new FileWriter("d:/text.txt")) {
            writer.write("hello world");
            // 手动刷新缓冲区 确保内容已写入
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果以后,打开上面text.txt观察里面内容:

 像上述写操作:

 其实是先写到缓冲区中(缓冲区有很多种形态),当写操作执行完毕后,内容可能在缓冲区里,还没有真的进入硬盘,close操作就会出发缓冲区的刷新(刷新操作就会把缓冲区的内容写到硬盘里面),除了close方法以外,flush方法也可以起到刷新缓冲区的效果.

5).Scanner搭配流对象进行使用

这里的System.in 其实就是一个输入流对象.

public class IODemo10 {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("d:/text.txt")) {
            Scanner scanner = new Scanner(inputStream);
            scanner.next();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Scanner 的close本质上就是要关闭内部包含的这个流对象,但是此时内部的InputStream对象已经被

try() 关闭了,里面的Scanner 不关闭也没事

 🐯三、练习

接下来,通过两组典型案例,来增加对IO操作的理解以及掌握.

1.扫描目录,删除文件

实现思路:

1).首先,让用户输入一个扫描目录,然后判断用户输入的路径是不是目录,如果是的话,再让用户输入要删除的文件名.

2).因为文件系统是树形结构,所以我们使用深度优先遍历(递归)完成遍历.

3).通过使用File类的 listFiles() 方法返回值是File数组,可以列出所有用户输入的目录下的所有目录以及文件,这样的话,就可以通过遍历这个数组中的每个元素来进行判断,如果这个元素是目录的话就继续递归,如果不是目录(普通文件)判断是不是要删除的文件.

代码实现:

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

/**
 * 扫描目录 删除文件
 * @author 26568
 * @date 2023-01-12 14:19
 */
public class IODemo11 {
    private static Scanner scanner = new Scanner(System.in);
    public static void main(String[] args) {
        System.out.println("请输入你要搜索的路径");
        String basePath = scanner.next();
        // 针对用户输入的路径进行判断
        File root = new File(basePath);
        // 如果不是目录直接结束操作
        if (!root.isDirectory()) {
            // 路径不存在 或者只是一个普通文件 此时无法进行搜索
            System.out.println("输入的路径有误");
            return;
        }
        // 再让用户输入一个要删除的文件
        System.out.println("请输入要删除的文件名");
        String nameToDelete = scanner.next();
        // 使用该方法进行删除文件
        scanDir(root,nameToDelete);
    }

    private static void scanDir(File root, String nameToDelete) {
        //  打印当前目录
        System.out.println("[scanDir]"+root.getAbsolutePath());
        // 1.列出root下的文件和目录
        File[] files = root.listFiles();
        // 如果为空 说明当前目录没有要删除的文件名
        if (files == null) {
            // 结束递归
            return;
        }
        // 2.遍历当前files数组 判断是否包含要删除文件名
        for (File f:files) {
            // 先判断是不是目录
            if (f.isDirectory()) {
                // 继续递归
                scanDir(f,nameToDelete);
            } else {
                // 是普通文件 判断是否要删除
                // 如果包含要删除的文件名
                if (f.getName().contains(nameToDelete)) {
                    System.out.println("确认要删除"+f.getAbsolutePath()+"吗?");
                    String choice = scanner.next();
                    if (choice.equals("y") || choice.equals("Y")) {
                        f.delete();
                        System.out.println("删除成功");
                    } else {
                        System.out.println("删除取消");
                    }
                }
            }
        }
    }
}

运行结果

 

2.拷贝文件

 实现思路:

1).首先,让用户输入要拷贝的文件,然后再输入要拷贝的目的路径,如果输入的不是文件就直接返回, 如果输入的目的路径已经是一个文件了,也直接结束操作.

2).接下来开始拷贝,使用字节流来进行拷贝,每次读取一个字节,然后写入.

代码实现:

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

/**
 *
 * @author 26568
 * @date 2023-01-13 9:47
 */
// 拷贝文件
public class Exercise {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入你要拷贝的文件");
        String root = scanner.next();
        System.out.println("请输入目的路径");
        String dest = scanner.next();
        // 判断输入的路径是否有误
        File rootFile = new File(root);
        if (!rootFile.isFile()) {
            // 如果不是文件
            System.out.println("您输入的文件路径有误");
            return;
        }
        File destFile = new File(dest);
        if (destFile.isFile()) {
            // 如果已经是文件
            System.out.println("您输入的目的路径有误");
            return;
        }
        // 进行拷贝
        try(InputStream inputStream = new FileInputStream(rootFile);
            OutputStream outputStream = new FileOutputStream(destFile)) {
            // 一个字节一个字节进行拷贝
            while (true) {
                int b = inputStream.read();
                if (b == -1) {
                    break;
                }
                outputStream.write((byte)b);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值