Java IO

本文介绍了Java中的File类,用于操作文件和目录,包括创建、删除、重命名等。接着讲解了IO流的概念,包括输入流、输出流、字节流、字符流,以及如何使用缓冲流提高效率。还介绍了try-with-resources语句用于自动关闭资源。最后提到了序列化和反序列化,用于对象的持久化存储。
摘要由CSDN通过智能技术生成

IO流

主要内容

  • File类
  • IO流介绍
  • try-with-source
  • 缓冲流
  • 序列化与反序列化

学习目标

知识点要求
File类掌握
IO流介绍掌握
try-with-source掌握
缓冲流掌握
序列化与反序列化掌握

一、File类

1. 介绍

在JDK中存在一个java.io.File类,这个类对应的是操作系统中的一个文件一个文件夹(目录)

通过File类可以实现操作系统中文件|文件夹的创建、删除、查看、重命名等操作。

2. 路径强调

File类操作资源时有两种路径写法:

​ 1. 全路径。即:从磁盘名称开始的路径。

​ 2. 项目路径。即:从项目跟目录开始的路径。

在windows中,文件路径都是通过右斜杠\表示子目录。

例如:在D盘下名称为soft的的文件夹,在soft文件夹中有名称为jdk的文件夹。对应的路径为:

在Java中如果字符串的值中出现右斜线表示转义字符。会对后面的一个字符进行转义。所以在Java中如果想要表示上面的路径可以使用两个右斜线或一个左斜线。

对应windows中D:\soft\jdk的路径,在Java中路径的字符串写法为

3. File类构造方法

File 一共提供了四个构造方法,都是有参构造,并没有提供我们能使用的无参构造。其中我们比较常用的是File(String)和File(String,String)和File(File,String)

  1. File(String): 参数表示文件或文件夹全路径。

  2. File(String,String): 中第一个参数表示父文件夹路径,第二个参数表示资源路径。

  3. File(File,String): 第一个参数表示父文件夹对象,第二个参数表示资源路径。

4. 创建文件夹

File中有两个方法能够创建文件夹。

mkDir():创建路径中最后一层文件夹。如果希望创建的文件夹已经存在或路径中前面文件夹不存在,返回false。

mkDirs():创建文件夹,如果路径中的文件夹不存在,先创建路径中的文件夹, 然后再创建指定的文件夹。返回值表示是否创建成功。

​ 代码示例:

public static void main(String[] args) {
 //只会创建TEST1,如果zwx666不存在创建失败
  File file1 = new File("D:\\zwx666\\TEST1");
  boolean mkdir = file1.mkdir();
  System.out.println(mkdir);


  //zwx666不存在会先创建zwx666在创建TEST2
  File file2 = new File("D:\\zwx666\\TEST2");
  boolean mkdirs = file2.mkdirs();
  System.out.println(mkdirs);
    
}

5. 判断资源是否存在

exists(): 判断资源是否存在。存在返回true,不存在返回false。

代码示例:

public static void main(String[] args) {
  File file = new File("D:/io");
  System.out.println(file.exists());//true表示存在,false表示不存在
}

6. 创建空文件

createNewFile() 方法可以创建一个空文件,要求文件所在目录必须真实存在。

JDK在定义这个方法时抛出了IOException(Checked异常),文件路径不正确、无法操作文件等情况下抛出此异常。返回值表示是否创建成功。

public boolean createNewFile() throws IOException {
  SecurityManager security = System.getSecurityManager();
  if (security != null) security.checkWrite(path);
  if (isInvalid()) {
    throw new IOException("Invalid file path");
  }
  return fs.createFileExclusively(path);
}

示例代码:

public static void main(String[] args) {
        try {
            File file = new File("D:\\zwx666\\a.txt");
            System.out.println(file.createNewFile());
        } catch (IOException e) {
            e.printStackTrace();
        }

}
6.1 结果一

设定计算机中D盘下没有io文件夹。程序运行会出现异常。

6.2 结果二

如果磁盘D盘中存在io文件夹。程序运行结果输出:true。

并在io文件夹中创建一个a.txt文件。文件内容为空。

6.3 代码优化

为了防止新建文件时因为目录不存报异常,可以结合exists()和mkDirs()方法保证程序的稳定运行

public static void main(String[] args) {
    File parent = new File("D:\\zwxtest");
    if (!parent.exists()){
            parent.mkdirs();
    }

    File file = new File(parent, "a.txt");

    try {
        System.out.println(file.createNewFile());
        }catch (IOException e) {
        e.printStackTrace();
        }
}

7. 重命名文件

renameTo(File) 剪切。利用剪切特性,把资源剪切后放入到原路径中,起新名字,实现重命名功能。

public static void main(String[] args) {

 File file = new File("D:\\zwx666\\a.txt");

 System.out.println(file.renameTo(new File("D:\\zwx666\\b.txt")));//true
    
}

8. 删除文件

delete() 可以实现删除文件。如果文件成功删除返回true。如果文件不存在或删除失败返回false。

public static void main(String[] args) {
  File file = new File("D:/io/a.txt");
  boolean delete = file.delete();
  System.out.println(delete);
}

9. 判断资源是否为文件

isFile() 判断资源是否是文件。如果是文件返回true,如果是文件夹或不存在这个资源返回false。

public static void main(String[] args) {
  File file = new File("D:/io/a.txt");
  System.out.println(file.isFile());
}

10. 查看文件名和文件路径

getName() 文件名。

**getAbsolutePath() ** 文件在磁盘中全路径。

**getPath() ** 创建文件时指定的路径。

public static void main(String[] args) {
  File file = new File("abc/test.txt");
  System.out.println(file.getName()); //test.txt
  System.out.println(file.getAbsolutePath()); //D:\ideaws\abc\test.txt
  System.out.println(file.getPath()); //abc\test.txt
}

11. 查看目录中内容

list() 返回值为目录中资源的名称。返回值类型String[]。数组长度和资源个数相同。如果是空文件夹范围一个长度为0的数组对象。如果文件夹不存在返回null。

listFiles() 返回值为目录中资源对象。返回值类型File[]。数组长度和资源个数相同。如果是空文件夹范围一个长度为0的数组对象。如果文件夹不存在返回null。

public static void main(String[] args) {
  File file = new File("D:/io");
  //获取文件夹中资源名称
  String[] list = file.list();
  for (String s : list) {
    System.out.println(s);
  }
  //获取文件中资源对象
  File[] files = file.listFiles();
  for (File file1 : files) {
    System.out.println(file1);
  }
}

二、小节实战案例 - 打印目录结构

1. 需求

打印D盘中io文件夹及子文件夹中内容。

2. 代码实现

public static void main(String[] args) {
    //创建文件夹对象
    File file = new File("D:\zwx666");
    //调用方法 参数1: 文件夹对象 参数2: 空格数量
    test(file, 0);
}
public static void test(File file, int length){
    File[] files = file.listFiles();
    for (File file1 : files) {
        if(file1.isFile()){
            for (int i = 0; i < length; i++) {
                System.out.print(" ");
            }
            System.out.println(file1.getName());
        }else {
            for (int i = 0; i < length; i++) {
                System.out.print(" ");
            }
            System.out.println(file1.getName());
            //递归调用
            test(file1, length+1);
        }
    }
}

三、IO流

1. 介绍

流就是将数据无结构化的传递。强调的是数据的传输过程。

流分为输入流(Input)输出流(Output),所以简称为I/O流。

输入流表示从一个源读取数据,输出流表示向一个目标写数据。

Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。

输入也叫做读取数据,输出也叫做作写出数据

输入流: 从磁盘读到内存中

输出流 : 从内存写到磁盘中

配合使用:

Java中的流是对字节序列的抽象,我们可以想象有一个水管,只不过现在流动在水管中的不再是水,而是字节序列。和水流一样,Java中的流也具有一个“流动的方向”,通常可以从中读入一个字节序列的对象被称为输入流;能够向其写入一个字节序列的对象被称为输出流。

输入输出流是一个相对概念。主要看以谁为参照物。如果数据是从参照物出去就是输出流。如果数据是往参照物进就是输入流。

例如:我们一直用的控制台输入。用户在控制台输入的内容,会存储到程序中的一个变量里。出发点是控制台,目的地是程序中,流是把输入内容传递到变量的过程,这种流就是输入流。当我们使用一个输出语句打印文字时,文字的出发点是应用程序,目的地是控制台,流负责把变量对应内容运输到控制台,这种流就是输出流。

对于Java程序来说,输入输出流都是相对应用程序来说的

总结

​ 1. 流是一个抽象概念。流是看不见的。

​ 2. 流有出发点和目的地。相对出发点来说就是输出流,相对目的地来说就是输入流。

​ 3. 流是数据运输的载体。

2. Java中IO流分类

在JDK中提供了IO类的支持。这些类都在java.io包中。

  1. 根据方向的划分: 输入流和输出流

  2. 根据数据的单位划分: 字节流和字符流。

  3. 根据功能的划分: 节点流和处理流(缓冲流)

​ 字节流就是流中数据以字节为单位(Byte)。特别适用于音频、视频、图片等二进制文件的输入、输出操作。

​ 字符流就是流中数据以字符为单位。存在的意义:高效、方便的读取文本数据。此处需要注意的是字符流单位可能是一个字节,可能是多个字节。

3. Java中IO流体系图

4. Java中IO常用类体系图

虽然我们作为一个Java程序员要学习JDK,但是我们不可能把JDK中所有内容都记住,所以提取上图中我们比较常用的类如下:

四、字节输入输出流

1. 介绍

传输过程中,传输数据的最基本单位是字节。

FileInputStream和FileOutputStream是文件操作常用的两种流。我们可以借助这两个流实现文件读取、文件输出、文件拷贝等功能。

把硬盘上的文件读取到应用程序中使用FileInputStream。

把应用程序中文件内容输出到硬盘上使用FileOutputStream。

注意:

1. 流使用完毕一定要关闭,释放资源。

2. 如果输出流内容来源于输入流,要先关输出流后关输入流

2. FileInputStream API 介绍

3. FileOutputStream API 介绍

4. 字节输入流的使用

方式一:

//创建文件对象
File file = new File("D:/io/a.txt");
//使用字节输入流读取文件
FileInputStream fis = new FileInputStream(file);
while (true){
    //读取文件中的内容, 内容为-1, 表示已经全部读取
    int read = fis.read();
    if (read == -1){
        break;
    }
    System.out.println(read);
}
fis.close();

方式二:

public static void main(String[] args) throws Exception {
    //创建文件对象
    File file = new File("D:/io/a.txt");
    //使用字节输入流读取文件
    FileInputStream fis = new FileInputStream(file);
    int read;//读取文件中的内容
    while ((read = fis.read()) != -1){
        System.out.println(read);
    }
    fis.close();
}

5. 字节输出流的使用

public static void main(String[] args) throws Exception {
    //创建文件对象
    File file = new File("D:/io/d.txt");
    //创建字节输出流
    FileOutputStream fos = new FileOutputStream(file);
    //向文件中输出
    fos.write("abc".getBytes());
    fos.close();
}

五、小节实战案例 - 使用字节流复制文件

1. 需求

​ 1. 在D盘根目录包含文件test.png

​ 2. 把test.png复制一份,命名为test_copy.png(拷贝)

2. 实现思路

​ 1. 使用FileInputStream 读取test.png

​ 2. 创建FileOutputStream,指定输出目的地为D:/test_copy.png

​ 3. 遍历FileInputStream读取的数据,遍历的同时将读取的数据输出。

​ 4. 先关闭输出流,再关闭输入流

3. 代码示例

 public static void main(String[] args) throws Exception {
     //创建字节输入流读取文件
     FileInputStream fis = new FileInputStream(new File("d:/io/test.png"));
     //创建字节输出流输出文件
     FileOutputStream fos = new FileOutputStream(new File("d:/io/test_copy.png"));
     long l = System.currentTimeMillis();
     int red;
     while ((red = fis.read()) != -1){
         fos.write(red);
     }
     long l1 = System.currentTimeMillis();
     System.out.println(l1 - l);
     fis.close();
     fos.close();
 }

注意: 每次读取一个字节, 效率低

​ 建议使用数组进行读取, 每一次可以读写数组长度的数据, 效率较高

public static void main(String[] args) throws Exception {
    //创建字节输入流读取文件
    FileInputStream fis = new FileInputStream(new File("d:/io/test.png"));
    //创建字节输出流输出文件
    FileOutputStream fos = new FileOutputStream(new File("d:/io/test_copy.png"));
    long l = System.currentTimeMillis();
    byte[] bytes = new byte[1024];
    int length; //每一次读取的长度, 全部读取完成返回-1
    while ((length = fis.read(bytes)) != -1){
        //System.out.println(red);
        fos.write(bytes, 0, length);
    }
    long l1 = System.currentTimeMillis();
    System.out.println(l1 - l);
    fis.close();
    fos.close();
}

六、字符输入输出流

1. 介绍

传输过程中,传输数据最基本的单位是字符流。

由于字符编码方式不同,有时候一个字符使用的字节数也不一样,比如ASCLL方式编码的字符,占一个字节;而UTF-8方式编码的字符,一个英文字符需要一个字节,一个中文需要三个字节。

字节数据是二进制形式的,要转成我们能识别的正常字符,需要选择正确的编码方式。我们生活中遇到的乱码问题就是字节数据没有选择正确的编码方式来显示成字符。

因为数据编码的不同,因而有了对字符进行高效操作的流对象,字符流本质其实就是基于字节流读取时,去查了指定的码表,而字节流直接读取数据可能会有乱码的问题(中文会乱码)

2. BufferedReader API介绍

3. BufferedWriter API介绍

4. 字符输入流

public static void main(String[] args) throws Exception {
        //创建字符输入流对象
        FileReader fr = new FileReader("D:\\zwx666\\test.txt");
        int read1;
        while ((read1 = fr.read()) != -1){
            System.out.print((char) read1);//打卡打,不乱码
        }
        fr.close();

        //创建字节输入流对象
        FileInputStream fis = new FileInputStream(new File("D:\\zwx666\\test.txt"));
        int read2;
        while ((read2 = fis.read()) != -1){
            System.out.print((char) read2);//打å,乱码
        }
        fis.close();
    }

5. 字符输出流

public static void main(String[] args) throws Exception {
    /*
     * 参数1: 输出文件对象
     * 参数2: true: 追加
     *       false: 覆盖原有内容
     * */
    FileWriter fw = new FileWriter(new File("d:/io/aa.txt"),true);
    //可以写字符串
    fw.write("你好吗");
    fw.close();

    FileOutputStream fos = new FileOutputStream(new File("d:/io/abc.txt"));
    //只能写字节
    fos.write("你好吗".getBytes());
    fos.close();
}

总结:

其实只有字节流,没有字符流。字符流的底层还是字节流,进行了封装转换,是开发者可以更简单的来处理非英文字符

字节流可以完成所有类型文件的复制(文本、音频、视频、图片、chm)

字符流只可以完成文本文件的复制(txt、java),字符流一般用来处理中文的文本文件

七、try-with-source

1. 介绍

try-with-source 是java 7引入的。主要是为了解决因为忘记关闭资源而导致的性能问题和调用关闭方法而导致的代码结构乱的问题。

2. 语法

try(需要在finally关闭的资源对象定义,可以写多条代码){

}catch(){

}

我们发现和普通的try-catch相似, 但是在try后面多个一个括号。

定义到括号里面的对象会自动关闭资源。

3. 使用

推荐使用字符流, 将a.txt中的文本内容拷贝到b.txt中

//原写法
public static void main(String[] args) throws Exception {

        //1.创建字符输入流对象
        FileReader fr = new FileReader(new File("D:\\zwx666\\a.txt"));

        //2.创建字符输出流对象
        FileWriter fw = new FileWriter(new File("D:\\zwx666\\b.txt"), true);

        //3.读取
        char[] chars = new char[1024];
        int length;//每次读取的长度
        while ((length = fr.read(chars)) != -1){
            //4.输出
            fw.write(chars,0,length);
        }

        fr.close();
        fw.close();
    }
//使用了try-with-source
public static void main(String[] args) {
  try (
    //1.创建字符输入流对象
    FileReader fileReader = new FileReader("D:/io/a.txt");
    //2.创建字符输出流对象
    FileWriter fileWriter = new FileWriter("D:/io/b.txt");)
  {
    //3.读取
    char[] chars = new char[1024];
    int length; //每次读取的长度
    while ((length = fileReader.read(chars)) != -1) {
      //4.输出
      fileWriter.write(chars, 0, length);
    }
  } catch (IOException e) {
    e.printStackTrace();
  }

}

八、缓冲流

1. 介绍

Java IO中BufferedXXX相关的流统称缓冲流。其本质就是在输入输出时添加缓冲区,减少磁盘IO的次数, 这样可以提高读写效率,同时也可以反复读取。

缓冲流称为上层流(高效流),其底层必须有字节流或字符流。当使用完成后关闭缓冲流,字节流或字符流也会随之关闭。

可以使用flush()刷新(清空)缓冲区内容,把内容输出到目的地。

当缓存区满了以后会自动刷新。在代码中当关闭流时,底层自动调用flush()方法。

2. 字节缓冲流

public static void main(String[] args) throws Exception {
    //1.创建一个输入流和输出流
    InputStream fis = new FileInputStream(new File("e:/JDK_API.CHM"));
    OutputStream fos = new FileOutputStream(new File("e:/JDK_API2.CHM"));
    //默认输入缓冲区大小8192
    BufferedInputStream bis = new BufferedInputStream(fis);
    //默认输出缓冲区大小8192
    BufferedOutputStream bos = new BufferedOutputStream(fos);
    //2.使用输入流和输出流完成文件复制
    /* 
      int n; //读取一个字节,赋给n
      while((n = bis.read()) != -1){
          //写一个字节
          bos.write(n);
      }
    */
    byte[] bytes = new byte[1024];
    int n; //每次读取数组长度放入输入缓存池
    while((n = bis.read(bytes)) != -1){
        //输出数组中指定长度的字节到输出缓存池
        bos.write(bytes, 0, n);
    }
    //3.关闭输入流和输出流
    bis.close();
    bos.close();
}

3.原理图

image-20220325193421208

只要关闭高层流即可,底层流不用手动关闭;因为高层的关闭方法就是把底层流关闭

4. 字符缓冲流

public static void main(String[] args) throws Exception {
    //1.创建两个缓冲流
    BufferedReader br =
        new BufferedReader(new FileReader(new File("d:/io/a.txt")));
    BufferedWriter bw =
        new BufferedWriter(new FileWriter("d:/io/a_copy.txt"));
    //2.使用两个缓冲流完成按行输入, 输出的功能
    String str;//每次读取一行
    while((str = br.readLine()) != null ){
        bw.write(str); //每次写一行
        bw.newLine(); //bw.write("\r\n");不同操作系统中换行符是不同的
    }
    //关闭两个流
    br.close();
    bw.close();
}

总结1:BufferedReader和BufferedWriter的优点

  • 速度快
  • 简化编程

总结2:readLine()底层的原理

  • 底层还是一个一个字符的读取,append()放入到StringBuilder(或者char[] )中,遇到换行符 ,将StringBuilder(char[])转换成String并返回

总结3:不同的操作系统中换行符是不同的

  • Unix系统里,每行结尾只有“<换行>”,即“\n”;
  • Windows系统里面,每行结尾是“<回车><换行>”,即“\r\n”;
  • Mac系统里,每行结尾是“<回车>”,即“\r”。

九、小节实战案例 - 把文件中内容复制到另一个文件中

1. 需求

在D盘下有a.txt文件, 包含两行内容

​ 我的名字:

​ 张三

需要从a.txt中把

​ 我的名字:

​ 张三

复制到b.txt文件中。

2. 实现思路:

创建缓冲输入, 输出流,读取a.txt, 写到b.txt

使用readLine(), 循环读取

3. 代码实现

public static void main(String[] args) {
  try(
      //1.创建字符输入流缓冲区(创建字符输入流对象)
      BufferedReader br = new BufferedReader(new FileReader("D:/a.txt"));
      //2. 创建字符输出缓冲区(创建字符输出流 )
      BufferedWriter bw = new BufferedWriter(new FileWriter("D:/b.txt", true));)
  {
      String str;
      //每次读取 一行
      while ((str = br.readLine()) != null){
          //不会覆盖原始内容, 在原来内容后追加
          bw.write(str);
          //换行
          bw.newLine();
      }
      //手动刷新输出流缓冲区
      bw.flush();
  } catch (Exception e) {
      e.printStackTrace();
  }
}

十、序列化与反序列化

1. 介绍

序列化:把对象转换为字节数组。在java中通过ObjectOutputStream序列化。序列化后须接收序列化的结果。所以在ObjectOutputSteam构造方法需要要一个OutputStream或其子类输出流对象,这个对象就是负责接收序列化结果的。

反序列化:把字节数组转换为对象。在java中通过ObjectInputStream反序列化,反序列化的结果在内存,需要通过输入流对象接收反序列化结果。所以在ObjectInputStream构造方法参数必须要一个InputStream对象,这个对象就负责接收反序列化结果的。

**2. **为什么要序列化

序列化后对象就是字节数组。变为字节数组后就可以把数组中内容输出到本地硬盘中,在后面学习的网络通信中,数据传输时也需要将对象转换为字节。

3. 如何序列化

让需要序列化的类实现Serializable接口,实现了这个接口代表这个类允许被序列化。

通过Java中ObjectOutputStream把对象进行序列化, ObjectInputStream把对象反序列化。

4. 属性值不参与序列化

如果类中包含一些私密属性,例如: 密码等。可以通过transient关键字,禁止该属性值被序列化。

public class Student implements Serializable {
  private String name;
  private transient int age;
	
  //...
}

5. 序列码

在实现了Serializable接口的类中。如果没有显示添加序列码会由JVM生成一个。

程序员也可以自己显示添加一个序列码,这个序列码和类中代码有关系,如果类中内容不变序列化和反序列化是没有影响的。

但是在企业级项目中,难免碰见需要修改类结构的情况。例如:添加一个新的属性。当我们修改了类结构后,类中自动生成的序列码就会改变。但是要求序列化和反序列化时序列码必须相同。而由于序列码的改变,所以在反序列化时会出错。为了防止这种问题,开发中只要实现了序列化都会添加序列码。

5.1 把序列化结果输出到文件中
public static void main(String[] args) throws IOException, ClassNotFoundException {
  Student zwx = new Student("zwx", 18);
  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\zwx666\TEST3\a.txt"));
  oos.writeObject(zs);
}
5.2 把文件中内容反序列化为对象
public static void main(String[] args) throws IOException, ClassNotFoundException {
  ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\zwx666\TEST3\a.txt"));
  Student stu = (Student) ois.readObject();
  System.out.println(stu);
}
5.3 添加新的属性
public class Student implements Serializable {
  private String name;
  private int age;
  private String addr;
  //...
}

运行反序列化, 异常信息如下

Exception in thread "main" java.io.InvalidClassException: com.bjsxt.service.Student; local class incompatible: stream classdesc serialVersionUID = 7117420395380926872, local class serialVersionUID = -561887848319347330
	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:689)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2023)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1873)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2180)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1690)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:499)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:457)
	at com.bjsxt.test.Test.main(Test.java:12)
5.4 显示指定添加序列码

解决办法是在类中添加serialVersionUID ,需要注意的是,等号左侧的写法是固定的。等号右侧的值随意。

private static final long serialVersionUID = 123456789L;
5.5 解决修改代码序列码不一致
public class Student implements Serializable {
  private static final long serialVersionUID = 123456789L;
  private String name;
  private int age;
  //...
}
5.6 再次测试结果

重新序列化, 反序列化

十一、综合实战案例

完成登录注册功能。

1. 需求:

  1. 只允许SystemService的实现类中包含控制台的输入输出代码。

  2. 用户注册的数据使用序列化存储到当前项目根路径下user.txt文件中。

  3. 用户登录需要的数据使用反序列化读取user.txt

  4. 面向接口编程。

  5. 用户只需有用户名和密码即可。

  6. 用户包含登录注册两大功能。

2. 实现思路:

  1. 抽象用户类。包含用户名和密码属性。封装。

  2. 编写系统菜单显示。

  3. 调用UserService实现类的注册方法, 需要将注册的数据存储到本地磁盘

  4. 调用UserService实现类的登录方法, 需要读取本地磁盘的数据

3. 效果图

3.1 先注册, 再登录
3.2 重启程序, 直接登录
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值