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(); }