javaIO之随机读写

javaIO包提供了很多可以读写文件的类,这些类大部分就是读写整个文件,

或者是读写一行、一个字符等等,

但是如果想在文件的指定位置读写,就必然需要使用RandomAccessFile

这个类有一个文件指针方法seek(),可以跳转到文件的任意位置。

 接下来,我们实现基于这个类实现几个常用的操作。

一、在文件头部插入或尾部追加

首先我们实现比较简单的尾部追加,我们只需要获取文件尾部的指针,然后直接写即可

/**
     * 在文件末尾追加数据
     * 返回插入数据时初始的pos
     *
     * @param fileName
     * @param s
     */
    public static long append(String fileName, String s) {
        File file = new File(fileName);
        long length = file.length();
        try (RandomAccessFile rf = new RandomAccessFile(file, "rw")) {
            rf.seek(length);
            rf.write(s.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return length;
    }

在头部插入稍微要麻烦一些,因为直接write会覆盖pos所在位置的旧数据,

我们要想实现插入的效果,就需要把pos后面的所有数据先缓存起来。

然后重新定位pos,开始写新内容,写完新内容之后,再把缓存起来的数据追加到新内容后面。

/**
     * 在文件指定位置插入内容
     *
     * @param fileName
     * @param pos
     * @param s
     * @throws IOException
     */
    public static void insert(String fileName, int pos, String s) throws IOException {
        File tmp = File.createTempFile("tmp", null);
        try {
            File file = new File(fileName);
            tmp.deleteOnExit();
            try (RandomAccessFile rw = new RandomAccessFile(file, "rw")) {
                try (FileOutputStream tmpOut = new FileOutputStream(tmp);
                     FileInputStream tmpIn = new FileInputStream(tmp)) {
                    rw.seek(pos);
                    byte[] buffer = new byte[64];
                    //读取指针之后的所有内容,缓存到临时文件
                    int hasRead = 0;
                    while ((hasRead = rw.read(buffer)) > 0) {
                        tmpOut.write(buffer, 0, hasRead);
                    }
                    //重新定位指针
                    rw.seek(pos);
                    //在指针位置写入新内容
                    rw.write(s.getBytes(StandardCharsets.UTF_8));
                    //把缓存数据写回
                    while ((hasRead = tmpIn.read(buffer)) > 0) {
                        rw.write(buffer, 0, hasRead);
                    }
                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

二、在文件指定位置更新数据

在数据库中经常需要update某个表,在文件中update内容要稍微繁琐一些。

首先我们先实现在指定pos替换的功能。

基本的思路是根据pos和s0的长度定位到s0的尾部,然后从尾部开始读直到文件末尾,将这些内容写入缓存。

然后重新将文件指针定位到pos,先写s1,然后写缓存的内容。

这里有个需要特别注意的地方,如果s0的长度大于s1,就会出现虽然实际内容少了,但是文件长度不变的情况,

所以这里需要有一个判断,重设一下文件的长度

/**
     * 在文件指定位置替换内容
     *
     * @param fileName
     * @param pos
     * @param s0       原字符串
     * @param s1       目标字符串
     * @throws IOException
     */
    public static void update(String fileName, int pos, String s0, String s1) throws IOException {
        File tmp = File.createTempFile("tmp", null);
        try {
            File file = new File(fileName);
            long length = file.length();
            tmp.deleteOnExit();
            try (RandomAccessFile rw = new RandomAccessFile(file, "rw")) {
                try (FileOutputStream tmpOut = new FileOutputStream(tmp);
                     FileInputStream tmpIn = new FileInputStream(tmp)) {
                    rw.seek(pos + s0.length());
                    byte[] buffer = new byte[64];
                    //读取指针之后的所有内容
                    int hasRead = 0;
                    //先把s1和s0之后的内容缓存
                    while ((hasRead = rw.read(buffer)) > 0) {
                        tmpOut.write(buffer, 0, hasRead);
                    }
                    //重新定位指针
                    rw.seek(pos);
                    //在指针位置写入新内容
                    rw.write(s1.getBytes(StandardCharsets.UTF_8));
                    while ((hasRead = tmpIn.read(buffer)) > 0) {
                        rw.write(buffer, 0, hasRead);
                    }
                    //如果旧的字符串长度大于新字符串,会导致文件总长度变小,需要更新文件总长度
                    if (s0.length() > s1.length()) {
                        rw.setLength(length - (s0.length() - s1.length()));
                    }
                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

有时候我们不关心原有内容是什么,只知道pos和新的内容,这个时候就可以省略s0,直接更新当前行

下面是updateLine的实现

/**
     * 更新指定行的内容
     * @param fileName
     * @param pos
     * @param s1
     * @throws IOException
     */
    public static void updateLine(String fileName, int pos,  String s1) throws IOException {
        File tmp = File.createTempFile("tmp", null);
        try {
            File file = new File(fileName);
            long length = file.length();
            tmp.deleteOnExit();
            try (RandomAccessFile rw = new RandomAccessFile(file, "rw")) {
                try (FileOutputStream tmpOut = new FileOutputStream(tmp);
                     FileInputStream tmpIn = new FileInputStream(tmp)) {

                    rw.seek(pos );
                    String s0 = rw.readLine()+"\n";
                    byte[] buffer = new byte[64];
                    //读取指针之后的所有内容
                    int hasRead = 0;
                    //先把s1和s0之后的内容缓存
                    while ((hasRead = rw.read(buffer)) > 0) {
                        tmpOut.write(buffer, 0, hasRead);
                    }
                    //重新定位指针
                    rw.seek(pos);
                    //在指针位置写入新内容
                    rw.write(s1.getBytes(StandardCharsets.UTF_8));
                    while ((hasRead = tmpIn.read(buffer)) > 0) {
                        rw.write(buffer, 0, hasRead);
                    }
                    //如果旧的字符串长度大于新字符串,会导致文件总长度变小,需要更新文件总长度
                    if (s0.length() > s1.length()) {
                        rw.setLength(length - (s0.length() - s1.length()));
                    }
                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

三、删除一行数据

删除一行数据与插入一行类似,需要先缓存所在行后面的内容,

然后更新文件长度

这个地方有时候有效,有时候无效,还需要继续研究

/**
     * 删除pos所在行
     *
     * @param fileName
     * @param pos
     */
    public static void delete(String fileName, int pos) throws IOException {
        File tmp = File.createTempFile("tmp", null);
        try {
            File file = new File(fileName);
            long length = file.length();
            tmp.deleteOnExit();
            try (RandomAccessFile rw = new RandomAccessFile(file, "rws")) {
                try (FileOutputStream tmpOut = new FileOutputStream(tmp);
                     FileInputStream tmpIn = new FileInputStream(tmp)) {
                    rw.seek(pos);
                    String s = rw.readLine()+"\n";
                    System.out.println(s);
                    byte[] buffer = new byte[64];
                    //读取指针之后的所有内容
                    int hasRead = 0;
                    while ((hasRead = rw.read(buffer)) > 0) {
                        tmpOut.write(buffer, 0, hasRead);
                    }
                    //重新定位指针
                    rw.seek(pos);
                    while ((hasRead = tmpIn.read(buffer)) > 0) {
                        rw.write(buffer, 0, hasRead);
                    }
                    //删除数据会导致文件总长变小,需要更新
                    //readline返回的字符串没有换行符,这里需要加上
                    rw.setLength(length - s.length() );
                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

四、查询数据

查询数据相对来说是比较简单的,只需要将文件指针定位到指定位置,然后读取一行即可。

public static String select(String fileName, long pos) {
        File file = new File(fileName);
        try (RandomAccessFile ra = new RandomAccessFile(file, "r")) {
            ra.seek(pos);
            return ra.readLine();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

 读多行数据

/**
     * 读取多行内容
     * @param fileName
     * @param pos
     * @param n
     * @return
     */
    public static String select(String fileName, long pos,int n) {
        File file = new File(fileName);
        StringBuilder sb = new StringBuilder();
        try (RandomAccessFile ra = new RandomAccessFile(file, "r")) {
            ra.seek(pos);
            for (int i=0;i<n;i++) {
                sb.append(ra.readLine()).append("\n");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return sb.toString();
    }

 

posted @ 2023-04-24 14:05  Mars.wang  阅读(175)  评论(0编辑  收藏  举报