JavaSE核心API--随机读写文件流

java.io.RandomAccessFile

1)理论讲解:文件复制

代码演示:

    RandomAccessFile src = new RandomAccessFile("bee0.png", "r");// 创建一个RAF用于读取原文件
    RandomAccessFile desc = new RandomAccessFile("bee0_cp.png", "rw");// 再创建另一个RAF用于向复制文件中写入
    int d = -1;// 用于记录每次读取到的字节
    while ((d = src.read()) != -1) {
        desc.write(d);
    }
    System.out.println("复制完毕!");
    src.close();
    desc.close();

2)理论讲解:随机读写

传统的机械硬盘是由于其物理特性决定这单字节读写效率差 但是块读写效率是可以保证的

所以我们通过提高每次读取的数据量,减少实际读写的次数,可以提高读写的效率

随机读写:通常是单字节读写模式 块读写:一次读写一组字节的模式

代码演示:

    long start = System.currentTimeMillis();
    try (RandomAccessFile src = new RandomAccessFile("bee0.png", "r"))// 创建一个RAF用于读取原文件
    {
        RandomAccessFile desc = new RandomAccessFile("bee1_cp.png", "rw");// 再创建另一个RAF用于向复制文件中写入
        byte[] data = new byte[1024 * 10];// 每次实际读取到的字节量(一次读取10kb)
        int len = -1;
        while ((len = src.read(data)) != -1) {// 循环读取,直到文件末尾结束
            desc.write(data, 0, len);
        }
        desc.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    long end = System.currentTimeMillis();
    System.out.println("复制完毕!耗时:" + (end - start) + "ms");

3)小练习:简易记事本工具

程序启动后,要求用户输入一个文件名,然后开始对文件进行写操作 之后用户输入的每行字符串都写入到该文件中,当用户输入量单词"exit"时,程序退出。

代码演示:

    Scanner scan = new Scanner(System.in);
    System.out.println("请输入文件名(以.txt结尾):");
    String fileName = scan.nextLine();// 1.输入文件名
    while (true) {
        if (fileName.endsWith(".txt")) {// 2.判断文件名是否符合格式
            break;
        }
        System.out.println("输入格式错误,请重新输入!");
        fileName = scan.nextLine();
    }
    File file = new File(fileName);
    if (!file.exists()) {// 判断文件是否存在
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("文件创建成功!");
    } else {
        System.out.println("该文件已存在!");
        scan.close();
        return;
    }
    try (RandomAccessFile raf = new RandomAccessFile(fileName, "rw")) {// 3.使用RAF对该文件进行写操作
        /** 4.将用户输入的每行字符串写入到文件中 */
        byte[] data = {};
        String line = "";
        String lineFeed = "\n";
        String resultLine = "";
        System.out.println("开始写吧!");
        while (true) {
            line = scan.nextLine();
            if (line.equals("exit")) {
                break;
            }
            resultLine = line + lineFeed;// 用户输入一行字符串之后自动换行
            data = resultLine.getBytes("utf-8");
            raf.write(data);
        }
        System.out.println("写入完毕!");
    } catch (Exception e) {
        e.printStackTrace();
    }
    scan.close();

4)理论讲解:RandomAccessFile类

java.io.RandomAccessFile
用来读写文件数据的类,其基于指针对文件数据进行读写操作。

代码演示:

    /*
     * RandomAccessFile创建有两种模式:
     * r:只读模式,只读取文件数据,并不会写入内容
     * rw:读写模式,对文件既可以读也可以写。
     * 
     * 常见构造方法:
     * RandomAccessFile(String path,String mode)
     * RandomAccessFile(File file,String mode)
     * mode:创建的模式(r,rw)
     */
    /*
     * 对当前目录下的raf.dat文件进行读写操作
     * 对于"rw"模式创建时,若指定的文件不存在时会自动创建出来
     * 若为"r"只读模式时,若指定的文件不存在则会直接抛出异常:FileNotFoundException
     */
    RandomAccessFile raf=new RandomAccessFile("raf.dat","rw");
    /*
     * void write(int d)
     * 向文件中写入1个字节,写入的是给定的int值对应的
     * 2进制的“低八位”
     *                            vvvvvvvv
     * 00000000 00000000 00000000 00000001
     * 
     */
    raf.write(97);//0~255之间的数(1个byte字节)
    raf.write(100);
    System.out.println("写入完毕!");
    raf.close();

5)理论讲解:读写基本类型数据,以及RAF如何基于指针进行读写操作

代码演示:

    RandomAccessFile raf = new RandomAccessFile("raf.dat", "rw");
    /*
     * long getFilePointer() 
     * 获取指针位置
     */
    long pos = raf.getFilePointer();
    int max = Integer.MAX_VALUE;// 向文件中写入一个int最大值
    /*
     * 
     * 01111111 11111111 11111111 11111111 
     * max>>>24 
     * 00000000 00000000 00000000 01111111
     * 
     */
//  raf.write(max>>>24);
//  raf.write(max>>>16);
//  raf.write(max>>>8); 
//  raf.write(max);
    /*
     * void writeInt(int d) 
     * 一次性写入4字节,将给定的int值写出
     */
    raf.writeInt(max);
    System.out.println("pos:" + raf.getFilePointer());
    /*
     * 对应的基本类型数据都提供了写操作
     */
    raf.writeDouble(123.123);
    System.out.println("pos:" + raf.getFilePointer());
    raf.writeLong(123L);
    System.out.println("写出完毕!");
    /*
     * void seek(long pos) 
     * 将指针移动到指定位置
     */
    raf.seek(0);
    System.out.println("pos:" + raf.getFilePointer());
    /*
     * RAF提供了一组读取基本类型的方法 
     * int readInt() 
     * 连续读取4个字节,并还原为int值返回
     * 若在读取的过程中发现读取到了文件末尾,则直接抛出文件末尾异常:EOFException
     */
    int d = raf.readInt();
    System.out.println(d);
    /*
     * 读取double值 1.先将指针移动到double第一个字节的位置
     */
    raf.seek(8);
    double dou = raf.readDouble();
    System.out.println(dou);
    System.out.println("pos:" + raf.getFilePointer());

//  long lon = raf.readLong();
//  System.out.println(lon);
//  System.out.println("pos:"+raf.getFilePointer());
//  d = raf.read();
//  System.out.println(d);

    // 将double值的内容覆盖为567.567
    // 1.先将指针移动到double的第一个字节位置
    raf.seek(8);
    // 2.重新写入8个字节的新double值
    raf.writeDouble(567.567);
    System.out.println("double修改完毕!");
    System.out.println("pos:" + raf.getFilePointer());
    raf.seek(8);
    dou = raf.readDouble();
    System.out.println(dou);
    raf.close();

6)理论讲解:读取文本数据(字符串)

代码演示:

    /*
     * 字符串支持构造方法,将字节转换成字符串 
     * String(byte[] data) 
     * 按照系统默认字符集转换为字符串(不推荐) 
     * String(byte[] data,String csn) 
     * 按照指定的字符集将字节转换为对应字符串
     */
    try (RandomAccessFile raf = new RandomAccessFile("raf.txt", "r")) {// 打开只读模式
        byte[] data = new byte[(int) raf.length()];// 根据文件大小创建字节数组
        raf.read(data);// 一次性读取文件中的所有字节
        String str = new String(data, "UTF-8");// 将字节按照字符集还原成字符串
        System.out.println(str);
    } catch (Exception e) {
        e.printStackTrace();
    }

7)小练习:完成用户注册功能

要求用户输入注册信息包括:用户名,密码,昵称,年龄 
其中前三个为字符串,年龄为int值 
每个用户信息都写入到user.dat文件中保存
设计: 每条记录占用固定的100字节 其中:用户名,密码,昵称各占32字节,年龄占4字节
字符串故意留白有利于后期修改数据,并且格式固定,长度固定 
读取时效率高,但是会有部分空间上的浪费

代码演示:

    Scanner scan = new Scanner(System.in);
    System.out.println("欢迎注册!");
    System.out.println("请输入用户名:");
    String username = scan.nextLine();
    System.out.println("请输入密码:");
    String password = scan.nextLine();
    System.out.println("请输入昵称:");
    String nickname = scan.nextLine();
    System.out.println("请输入年龄:");
    int age = Integer.parseInt(scan.nextLine());

    /** 开启随机读写模式并往注册表文件上按顺序写入用户信息 */
    try (RandomAccessFile raf = new RandomAccessFile("user.dat", "rw")) {

        raf.seek(raf.length());// 先将指针移动到文件末尾处
        byte[] data = {};// 创建一个字节数组

        /** 写用户名 */
        data = username.getBytes("utf-8");// 1.先将用户名转换为一组字节
        data = Arrays.copyOf(data, 32);// 2.将该字节数组扩容至32字节
        raf.write(data);// 3.将32字节写入文件

        /** 写密码 */
        data = password.getBytes("utf-8");// 1.先将密码转换为一组字节
        data = Arrays.copyOf(data, 32);// 2.将该字节数组扩容至32字节
        raf.write(data);// 3.将32字节写入文件

        /** 写昵称 */
        data = nickname.getBytes("utf-8");// 1.先将昵称转换为一组字节
        data = Arrays.copyOf(data, 32);// 2.将该字节数组扩容至32字节
        raf.write(data);// 3.将32字节写入文件

        /** 写年龄 */
        raf.writeInt(age);// 将8字节的int型数据写入文件

        System.out.println("pos:" + raf.getFilePointer());
        System.out.println("注册完毕!");

    } catch (Exception e) {
        e.printStackTrace();
    }
    scan.close();

8)小练习:将user.dat文件中的每个用户信息读取出来并输出到控制台

代码演示:

    try (RandomAccessFile raf = new RandomAccessFile("user.dat", "r");) {
        for (int i = 0; i < raf.length() / 100; i++) {
            byte[] data = new byte[32];
            /** 读取用户名 */
            raf.read(data);
            String username = new String(data, "UTF-8").trim();
            /** 读取密码 */
            raf.read(data);
            String password = new String(data, "UTF-8").trim();
            /** 读取昵称 */
            raf.read(data);
            String nickname = new String(data, "UTF-8").trim();
            /** 读取年龄 */
            int age = raf.readInt();
            System.out.println("pos:" + raf.getFilePointer());
            System.out.println(username + "," + password + "," + nickname + "," + age);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

9)小练习:完成修改昵称功能

程序启动后,要求用户输入用户名及新的昵称,然后对user.dat文件中该用户的昵称进行修改,若文件中没有此用户则提示:没有此用户!

实现思路: 
  循环读取user.dat文件中的每条记录中的用户名,然后与用户输入的用户名进行比对
  若不是则进行下一次循环,若是该用户,则将指针移动到该条记录的昵称位置然后重写---将新的昵称以32字节形式写入以覆盖原昵称完成修改操作
  若循环完毕后仍然没有匹配到用户,则最终提示:没有此用户

代码演示:

    Scanner scan = new Scanner(System.in);
    System.out.println("请输入用户名:");
    String usernameInput = scan.nextLine();
    try (RandomAccessFile raf = new RandomAccessFile("user.dat", "rw");// 打开文件读写模式
    ) {
        /** 开始遍历注册表上的用户名进行查询 */
        for (int i = 0; i < raf.length() / 100; i++) {
            /*
             * 假设注册表上有3条记录 raf.length()=300字节/100=3条记录
             * 
             * i=0 开始读第1条记录 用户名的起始位置pos:0=i*100 i=1 开始读第2条记录 用户名的起始位置pos:100=i*100 i=2
             * 开始读第3条记录 用户名的起始位置pos:200=i*100
             */
            raf.seek(i * 100);// 每一次都先将指针放在用户名的起始位置开始读
            /** 读取用户名 */
            byte[] data = new byte[32];// 1.一次读取32个字节
            raf.read(data);// 2.块读文件
            String username = new String(data, "UTF-8").trim();// 3.将其还原为字符串(trim是为了去除字符串后面的留白部分)
            if (username.equals(usernameInput)) {// 如果读取注册表上的用户名和用户输入的用户名相匹配
                /** 修改昵称 */
                raf.seek(i * 100 + 64);// 将指针移动到该条记录的昵称位置
                System.out.println("请输入新的昵称:");
                String nickname = scan.nextLine();// 新的昵称由用户重新输入
                /** 重新写入昵称 */
                data = nickname.getBytes("utf-8");// 1.先将昵称转换为一组字节
                data = Arrays.copyOf(data, 32);// 2.将该组字节扩容至32字节
                raf.write(data);// 3.将32字节写入文件
                /** 修改密码 */
                raf.seek(i * 100 + 32);// 将指针移动到该条记录的密码位置
                System.out.println("请输入新的密码:");
                String password = scan.nextLine();// 新的密码由用户重新输入
                /** 重新写入密码 */
                data = password.getBytes("utf-8");// 1.先将密码转换为一组字节
                data = Arrays.copyOf(data, 32);// 2.将该组字节扩容至32字节
                raf.write(data);// 3.将32字节写入文件
                System.out.println("修改完毕!");
                scan.close();
                return;
            }
        }
        System.out.println("没有此用户!");
        scan.close();
    } catch (Exception e) {
        e.printStackTrace();
    }

10)理论讲解:写入字符串

代码演示:

    /*
     * String提供了将字符串转换为字节的方法:
     * byte[] getBytes()
     * 按照系统默认字符集将当前字符串转换为对应的字节,不推荐这种方法,依赖系统默认字符集不利于跨平台
     * 
     * byte[] getBytes(String csn)
     * 按照指定的字符集csn(charset name)转换为一组字节
     * 常见字符集:
     * GBK:国标编码,其中英文1字节,中文2字节
     * UTF-8:unicode的字符集编码,其中英文1字节,中文3字节。utf-8支持世界流行的所有文字
     *          所以也成为万国码,互联网最常用字符集
     * ISO8859-1:一种欧洲的编码集,不支持中文
     */
    RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");//打开读写模式
    byte[] data = new byte[1024*10];//块读写模式
    String str = "好嗨呦~";//要写入的字符串
    data = str.getBytes("utf-8");//把该字符串按照字符集转换成字节数组
    raf.write(data);//将该数组写入文件

    str = "感觉人生已经到达了高潮~";
    data = str.getBytes("utf-8");
    raf.write(data);

    System.out.println("写入完毕!");
    raf.close();