一、File类

1、File类的定义

File类当作一种特殊类型的文件,即文件名单列表,只能代表一个文件,不能访问文件的内容。只能对文件和目录进行新建、删除、重名名等操作。

2、创建File对象

构造方法:File(String pathname),抽象路径应尽量使用相对路径

3、相对路径和绝对路径

  • 绝对路径:无论当前工作目录如何,始终指向文件系统中的相同位置的路径(路径以盘符或/开头)
  • 相对路径:从某个给定的工作目录开始到目标位置的路径(路径不能以盘符或/开头)
/**
 * @document: File对象创建,File可以表示一个文件或者目录
 * @Author:SmallG
 * @CreateTime:2023/8/2+14:06
 */

public class Demo01 {
    public static void main(String[] args) {
        //指定文件路径  相对路径
        File file = new File("src/day03/demo.txt");

        //获取文件名
        String fileName = file.getName();
        System.out.println(fileName); //demo.txt

        //查看文件大小 单位字节
        long length = file.length();
        System.out.println(length); //37

        //文件是否可读
        boolean canRead = file.canRead();
        boolean canWrite = file.canWrite(); // 文件是否可写
        System.out.println("可读:" + canRead);
        System.out.println("可写:" + canWrite);

        //查看是否被隐藏
        System.out.println("是否被隐藏:" + file.isHidden());
    }
}

4、File类的常用操作

  • length():返回文件的长度,即占用的空间大小
  • exists():测试路径是否存在,返回值是boolean类型
  • createNewFile():当文件不存在时,自动创建文件,返回值是boolean类型
  • delete():删除此抽象路径名表示的文件或目录,返回值是boolean类型,删除目录时,要保证是空目录才能成功删除。删除时比较暴力,直接删除回收站里不会留存。
/**
 * @document: File 的常用操作
 * @Author:SmallG
 * @CreateTime:2023/8/2+14:35
 */

public class Demo02 {
    public static void main(String[] args) throws IOException {
        //创建一个不存在的文件
        File file = new File("F:/hello/hello.txt");
        //判断文件是否存在
        if (!file.exists()) { //如果不存在,创建文件
            //createNewFile()方法,如果文件不存在就创建
            boolean success = file.createNewFile(); //有返回值 boolean类型,true则代表创建成功
            System.out.println(success);

        } else {
            System.out.println("文件已存在");
        }
    }
}
/**
 * @document: 删除文件
 * @Author:SmallG
 * @CreateTime:2023/8/2+15:05
 */

public class Demo03 {
    public static void main(String[] args) {
        File file = new File("F:/hello/hello.txt");
        if (file.exists()) {
            System.out.println(file.delete());
        } else {
            System.out.println("文件不存在");
        }

        //删除目录
        File file1 = new File("F:/hello");
        if (file1.exists()) {
            System.out.println(file1.delete());
        }
    }
}

5、File创建目录

1、isDirectory() 方法:判断当前File表示的是否为一个目录,返回 boolean 类型

2、mkdir() 方法:

  • 创建此抽象路径名指定的目录
  • 当且仅当已创建目录时,返回 true;否则返回 false

3、mkdirs() 方法:

  • 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录
  • 当且仅当已创建目录以及所有必需的父目录时,返回 true;否则返回 false
  • 注意:此操作失败时也可能已经成功地创建了一部分必需的父目录
/**
 * @document: 创建目录
 * @Author:SmallG
 * @CreateTime:2023/8/2+15:14
 */

public class Demo04 {
    public static void main(String[] args) {
        //在F盘下创建一个hello目录
        File dir = new File("F:/hello");
        //判断目录是否存在
        if (!dir.exists()) {
            //如果不存在,创建目录
            boolean flag = dir.mkdir(); //返回一个boolean类型的值
            System.out.println(flag); //true则目录创建成功
        } else {
            System.out.println("目录已存在");
        }

        //创建多级目录 F:/hello/world/d1/d2
        File dirs = new File("F:/hello/world/d1/d2");
        if (!dirs.exists()) {
            boolean flag = dirs.mkdirs(); //返回一个boolean类型的值
            System.out.println("多级目录创建" + flag);
        } else {
            System.out.println("目录已存在");
        }
    }
}

6、获取目录的所有子项

listFiles()方法,返回一个抽象录路径名数组,表示目录中的子项(文件或目录)

/**
 * @document: 获取一个目录的所有子目录
 * @Author:SmallG
 * @CreateTime:2023/8/2+15:27
 */

public class Demo05 {
    public static void main(String[] args) {
        File dir = new File("src/day03");
        //判断当前文件是否为目录
        if (dir.isDirectory()) {
            //获取一个目录中所有的子项
            File[] files = dir.listFiles(); //返回一个数组
            System.out.println("目录中的文件/目录数量:" + files.length);
            //获取文件的路径
            System.out.println(Arrays.toString(files));
        } else {
            System.out.println("当前不是一个目录");
        }
    }
}

7、方法的递归

/**
 * @document: 递归运算 计算n的阶乘
 * @Author:SmallG
 * @CreateTime:2023/8/2+16:18
 */

public class Demo07 {
    public static void main(String[] args) {
        int n = 5;
        //计算n的阶乘
        System.out.println(factorial(n));
    }

    public static int factorial(int n) {
        //递归终止条件 n == 1时,结束递归
        if (n == 1) {
            return 1;
        } else {
            return n * factorial(n - 1);
        }
    }
}

8、求文件夹的大小问题

文件夹大小=文件夹下各级文件中所有文件的大小之和

/**
 * @document: 求文件的大小
 * @Author:SmallG
 * @CreateTime:2023/8/2+16:36
 */

public class Demo08 {
    public static void main(String[] args) {
        File file = new File("C:/Program Files/Internet Explorer");
        long sum = getSize(file);

        System.out.println("当前目录大小为:" + sum / 1024 / 1024 + "Mb"); //2Mb
    }

    public static long getSize(File file) {
        //判断是不是文件
        if (file.isFile()) {
            return file.length();
        } else {
            //获取当前目录下的所有子项
            long sum = 0;
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                sum += getSize(files[i]);
            }
            return sum;
        }
    }
}

9、FileFilter接口

用于抽象路径名的过滤器,此接口的实例可以传递给File类的listFiles(FileFilter)方法,用于返回满足该过滤器要求的子项

/**
 * @document: FileFilter过滤器
 * @Author:SmallG
 * @CreateTime:2023/8/2+17:19
 */

public class Demo09 {
    public static void main(String[] args) {
        File dir = new File("C:/Program Files/Internet Explorer");
        if (dir.isDirectory()) {
            //创建过滤器规则 匿名内部类
            FileFilter filter = new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    //先得到文件名
                    String fileName = pathname.getName();
                    System.out.println("正在过滤:" + fileName + "文件");
                    return fileName.startsWith("i"); //文件名是否以i开头 是的话返回true
                }
            };
            File[] files = dir.listFiles(filter);
            System.out.println("以i开头的数量:" + files.length);
            System.out.println(Arrays.toString(files));
        }
        
    }
}

二、I/O流

1、什么是I/O流?

input和output,是输入输出流,流是一组有序的数据序列,也可以称为数据流。

文件保存在硬盘中,程序的运行都在内存中运行

输出流

到计算机内存中----->输入流

从内存中出来-------->输出流

2、I/O流分类

  • 按使用方式:节点流与处理流,也称为基础流和高级流
  • 按照数据流的方向:输入流、输出流
  • 按处理数据单位:字节流、字符流

IO流分类

3、字节流

字节流是指数据流中的数据以字节为单位进行操作,主要用于处理二进制数据。流用完后要记得close()

  • InputStream和OutputStream:字节流的核心类,是两个抽象类,定义了基础的数据流读写方法,字节流中的其他类均为这两个类的子类。
  • FileInputStream和FileOutputStream:字节流中最为常用的类,分别继承InputStream和OutputStream,属于基础流。
  • BufferedInputStream和BufferedOutputStream:是字节流中较为常用的高级流,间接继承InputStream和OutputStream,主要提供了缓冲区功能。

(1)创建FOS对象

1.FileOutputStream:文件的字节输出流,可以以字节为单位将数据写入文件。如果指定文件已经包含内容,那么FOS对其写入数据时会将原有数据全部清除。

构造方法:

  • FileOutputStream(File file):创建一个向指定File对象表示的文件中写数据的文件输出流
  • FileOutputStream(String filename):创建一个向具有指定名称的文件中写数据的文件输出流

2.不清除原有数据的构造方法

  • FileOutputStream(File file,boolean append):创建一个指向对象的文件写数据的文件输出流,若为true则在末尾添加。
  • FileOutputStream(String filename,boolean append):创建一个指向名称的文件写数据的文件输出流,若为true则在末尾添加。
/**
 * @document: 字节文件输出流
 * @Author:SmallG
 * @CreateTime:2023/8/3+9:16
 */

public class Demo10 {
    public static void main(String[] args) throws IOException {
        File file = new File("F:/hello.txt");
        // FileOutputStream fos = new FileOutputStream(file); 传入文件名

        //普通的创建方法  在写入时会将之前所有的内容覆盖  可以在append参数里面添加个true
        FileOutputStream fos = new FileOutputStream("F:/hello.txt",true);
        String str1 = "\n第一次写入内容:hello world";

        //第二次写入内容
        String str2 = "\n第二次写入内容:SmallG帅锅\n";
        //字符串转换成字节数组
        byte[] s1 = str1.getBytes(StandardCharsets.UTF_8); //utf-8统一字符编码
        byte[] s2 = str2.getBytes();
        fos.write(s1);
        fos.write(s2);
        fos.close(); //关闭流
    }
}

(2)创建FIS对象

FileInputStream:文件的字节输入流,使用该流可以以字节为单位从文件中读取数据

构造方法:

  • FileInputStream(File file):创建从指定File对象表示的文件中读取数据的文件输入流
  • FileInputStream(String name):创建用于读取路径名所指的文件的文件输入流
/**
 * @document: 字节文件输入流
 * @Author:SmallG
 * @CreateTime:2023/8/3+9:37
 */

public class Demo11 {
    public static void main(String[] args) throws IOException {
        // 创建fis对象
        FileInputStream fis = new FileInputStream("F:/hello.txt");

        //创建字节数组作为读取文件的容器
        byte[] data = new byte[1024]; //创建1KB为单位的
        //读取数据到程序中 读取1kb  返回值是int类型
        int len = fis.read(data); //1次读1K 如果文件没有1K 用len变量记录实际读多少 单位:字节
        System.out.println("实际读取了:" + len + "字节");

        //把读取到的数据转换为字符串 从数组的第一位到data的长度为结束
        String str = new String(data, 0, len);
        System.out.println(str);
        //关闭
        fis.close();
    }
}

(3)复制文件

/**
 * @document: 文件复制
 * @Author:SmallG
 * @CreateTime:2023/8/3+10:17
 */

//1、把文件读取到内存中
//2、把内存中的数据写到新的文件中
public class Demo12 {
    public static void main(String[] args) throws IOException {
        //创建输入流 用于读取文件
        FileInputStream fis = new FileInputStream("F:/hello.txt");
        //创建输出流 用于写入文件
        FileOutputStream fos = new FileOutputStream("D:/hello.txt");
        //定义一个用于存放读取到的文件内容
        byte[] data = new byte[1024]; //一次只读1k
        //循环读取 因为文件的大小不确定 因此不知道要读取多少次
        int len = -1;
        while ((len = fis.read(data)) != -1) {
            String str = new String(data, 0, len, "utf-8");
            fos.write(data, 0, len);  //读了多少写多少
        }
        //关闭数据流 关闭原则:后使用的先关闭
        fos.close();
        fis.close();
    }
}

4、缓冲流

用于提高读写信息的效率,是一种处理流

(1)BIS与BOS

  • BufferedInputStream和BufferedOutputStream称为字节缓存流
  • BufferedInputStream:缓冲字节输入流内部维护着一个缓冲区,使用时该流会尽可能多的一次读取若干字节存入缓冲区,如何逐一但返回字节,直到缓冲区的数据全部读取完毕,然后再读取若干字节存入缓冲区,循环往复。
  • BufferedOutputStream:缓冲输出流内部维护一个缓冲区(字节数组)。缓冲区满,数据会一次性全部写出。
/**
 * @document: 缓冲流:提高文件读取的效率 降低频繁访问硬盘的速度
 * 1、BufferedInputStream
 * 2、BufferedOutputStream
 * @Author:SmallG
 * @CreateTime:2023/8/3+11:17
 */

public class Demo13 {
    public static void main(String[] args) throws IOException {
        //读取本地文件
        FileInputStream fis = new FileInputStream("C:/Git-2.33.0.2-64-bit.exe");
        //默认能缓冲8192字节
        BufferedInputStream bis = new BufferedInputStream(fis);

        FileOutputStream fos = new FileOutputStream("D:/new");
        //默认8192字节
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        int len = -1;
        while ((len = bis.read()) != -1) {
            bos.write(len);
        }
        bos.close();  //在关闭缓冲流时 默认会先调用flush()方法 清除缓存
        bis.close();
        fos.close();
        fis.close();
    }
}

(2)flush方法

  1. 输出缓冲流提供了flush方法:强制将当前缓冲区中缓存的字节一次性写出,随需随取。
  2. 输出缓冲流的close方法中默认也会调用一次flush方法,保证清空缓冲区。

5、序列化和反序列化

*对象序列化的条件:对象的类必须实现Serializable接口,同时类中的所有属性必须实现Serializable接口。

(1)对象序列化概念

  • 对象存储于内存之中,当需要将对象保存在硬盘上或者传输到另一台计算机上时,会用到对象的序列化。
  • 对象---------->字节序列(对象序列化)。
  • 字节序列----->对象(对象的反序列化)。

(2)OIS与OOS

java中序列化和反序列化通过对象流实现

  • ObjectOutputStream:将对象进行序列化输出流(对象-------->字节序列)
  • ObjectInputStream:将对象进行反序列化的输入流(字节序列------->对象)

(3)Serializable接口

这个接口创建的对象就是可序列化的对象,(Java中的包装类和String类均实现了Serializable接口),该接口中的方法对程序是不可见的,因此实现接口的类不需要实现额外的方法,只是作为可序列化的标志。

(4)transient关键字

被transient关键字修饰的属性再序列化是其值将被忽略。忽略掉一些属性,不让他序列化。

Person类:

/**
 * @document:完成序列化
 * @Author:SmallG
 * @CreateTime:2023/8/3+14:13
 */

public class Person implements Serializable {
    //创建属性
    private String name;
    private int age;
    private char gender;
    private transient String[] otherInfo;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", otherInfo=" + Arrays.toString(otherInfo) +
                '}';
    }

    public Person(String name, int age, char gender, String[] otherInfo) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.otherInfo = otherInfo;
    }

    public Person() {
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

    public String[] getOtherInfo() {
        return otherInfo;
    }

    public void setOtherInfo(String[] otherInfo) {
        this.otherInfo = otherInfo;
    }
}
/**
 * @document: 对象序列化
 * @Author:SmallG
 * @CreateTime:2023/8/3+14:19
 */

public class Demo14 {
    public static void main(String[] args) throws IOException {
        //首先创建一个对象
        Person person = new Person("SmallG", 23, '男', new String[]{"黄色", "超帅", "帅锅"});
        /*
         * 1、先把对象通过对象输出流写入到文件中 ---->对象序列化
         * 2、再把文件保存在硬盘中 这个过程称为数据持久化
         * */
        FileOutputStream fos = new FileOutputStream("F:/person.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        //写入文件
        oos.writeObject(person);
        oos.close();
        fos.close();
    }
}

反序列化:

/**
 * @document: 反序列化
 * @Author:SmallG
 * @CreateTime:2023/8/3+14:30
 */

public class Demo15 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //首先指定文件
        FileInputStream fis = new FileInputStream("F:/person.txt");
        //创建对象输入流,读取对象内容
        ObjectInputStream ois = new ObjectInputStream(fis);
        //转化为对象 (需要强转)
        Person person = (Person) ois.readObject();
        System.out.println(person);
        ois.close();
        fis.close();
    }
}

三、练习

1 读取文本文件中的每一行数据信息,链接后再存入最后一行

文本文件pw.txt的内容如下所示:

练习1

操作完毕之后的文本文件pw.txt的内容如下所示:

练习2

/**
 * @document: 读取文本文件中的每一行数据信息,链接后再存入最后一行 字节流
 * @Author:SmallG
 * @CreateTime:2023/8/2+15:47
 */

public class ReadFile {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("src/day03/homework/pw.txt");
        FileOutputStream fos = new FileOutputStream("src/day03/homework/pw.txt", true);
        byte[] data = new byte[1024];
        int len = -1;
        StringBuilder builder = new StringBuilder();
        while ((len = fis.read(data)) != -1) {
            String str = new String(data, 0, len);
            builder.append(str.replace("\n","").replace("\r",""));
        }
        byte[] s = builder.toString().getBytes();
        fos.write(s);
        fos.close();
        fis.close();
    }
}

2 实现简单的注册登录程序

编写一个程序,可以实现单个用户的简单注册和登录功能。

注册是保存用户输入的用户名和密码;登录是使用用户输入的用户名和密码与用户注册时的信息进行比对。

该程序的重点在于用户信息的持久化存储:之前编写的程序,数据都是临时存储在内存中,程序一旦关闭,用户输入的数据将被清除。本例需要实现的效果是当用户注册成功后,即使程序重启,也能够正常实现登录操作。

具体要求:

1、提供简单的用户交互菜单

2、用户注册时,利用序列化实现用户注册信息的持久化存储,存储位置为当前包,存储文件名为user.obj

3、用户登录时,利用反序列化实现用户注册信息的读取,和用户登录使用的用户名和密码进行比对,返回比对结果

4、仅需实现单个用户的注册和登录功能即可,也就是说新注册的用户会覆盖之前用户的注册信息

程序运行效果如下所示:

练习3

/**
 * @document: 用户类
 * @Author:SmallG
 * @CreateTime:2023/8/3+18:19
 */

public class User implements Serializable {
    String name;
    String password;

    public User() {
    }

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
/**
 * @document:
 * @Author:SmallG
 * @CreateTime:2023/8/3+18:26
 */

public class LoginSetup {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Scanner scanner = new Scanner(System.in);
        boolean flag = true;
        while (flag) {
            System.out.println("---------主菜单---------");
            System.out.println("        请选择功能:");
            System.out.println("        1 注册");
            System.out.println("        2 登录");
            System.out.println("        3 退出");
            System.out.println("请输入:");
            int n = scanner.nextInt();
            switch (n) {
                case 1:
                    System.out.println("---------注册---------");
                    System.out.println("请输入用户名:");
                    String name = scanner.next();
                    System.out.println("请输入密码:");
                    String password = scanner.next();
                    System.out.println("请再次输入密码:");
                    String repassword = scanner.next();
                    if (!password.equals(repassword)) {
                        System.out.println("两次密码不一致,注册失败");
                    } else {
                        User user = new User(name, password);
                        setup(user);
                        System.out.println("注册成功");
                    }
                    break;
                case 2:
                    System.out.println("---------登录---------");
                    System.out.println("请输入用户名:");
                    String name1 = scanner.next();
                    System.out.println("请输入密码");
                    String password1 = scanner.next();
                    boolean flag1 = login(name1, password1);
                    if (flag1) {
                        System.out.println("登录成功");
                    } else {
                        System.out.println("登录失败");
                    }
                    break;
                case 3:
                    System.out.println("退出成功");
                    flag = false;
                    break;
                default:
                    System.out.println("输入序号有误,请重新输入!");
                    break;
            }

        }


    }

    //登录方法
    public static boolean login(String name, String password) throws IOException, ClassNotFoundException {
        File file = new File("src/day03/homework/user.obj");
        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream ois = new ObjectInputStream(fis);
        if (!file.exists()) {
            return false;
        }

        User user = (User) ois.readObject();
        if (user != null) {
            if (name.equals(user.name) && password.equals(user.password)) {
                return true;
            } else {
                return false;
            }
        }
        return false;
    }

    //注册
    public static void setup(User user) throws IOException {
        try (
                FileOutputStream fos = new FileOutputStream("src/day03/homework/user.obj");
                ObjectOutputStream oos = new ObjectOutputStream(fos);

        ) {
            oos.writeObject(user);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

3 汉诺塔问题

据传开天辟地之神勃拉玛在一个庙里留下了三根金刚石的棒,第一根上面套着64个圆的金片,最大的一个在底下,其余一个比一个小,依次叠上去,庙里的众僧不倦地把它们一个个地从这根棒搬到另一根棒上,规定可利用中间的一根棒作为帮助,但每次只能搬一个,而且大的不能放在小的上面。就是这看似简单的问题,却困扰了人们千年以上。

后来,这个传说就演变为汉诺塔游戏,玩法如下:

1、有三根杆子A,B,C,A杆上有若干碟子

2、每次移动一块碟子,小的只能叠在大的上面

3、把所有碟子从A杆全部移到C杆上

你的任务是精确计算出到底需要移动多少次。很明显,如果只有2个圆盘,需要移动3次。圆盘数为3,则需要移动7次。如下图所示:

练习4

提示:基于递归的解决方案。

程序运行效果如下所示:

练习5

/**
 * @document: 汉诺塔
 * @Author:SmallG
 * @CreateTime:2023/8/2+16:10
 */

public class Han {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入盘数:");
        int n = scanner.nextInt();
        move(n, 'A', 'B', 'C');
        System.out.println("移动步数:" + count);
    }

    static int count = 0;

    public static void move(int n, char A, char B, char C) {
        if (n == 1) {
            System.out.println("盘 " + n + " 由 " + A + " 移至 " + C);
        } else {
            move(n - 1, A, C, B);
            System.out.println("盘 " + n + " 由 " + A + " 移至 " + C);
            move(n - 1, B, A, C);
        }
        count++;
    }
}

汉诺塔输入3时的解析图

最后修改:2023 年 08 月 03 日
如果觉得我的文章对你有用,请随意赞赏