小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
文章目录
介绍
java.io.RandomAccessFile
1、读写文件的工具
2、将文件中的字节数据,当作数组,用下标访问指定位置的字节值
RandomAccessFile 既可以读取文件内容,也可以向文件输出数据。同时,RandomAccessFile 支持“随机访问”的方式,程序快可以直接跳转到文件的任意地方来读写数据。
由于 RandomAccessFile 可以自由访问文件的任意位置,所以如果需要访问文件的部分内容,而不是把文件从头读到尾,使用 RandomAccessFile 将是更好的选择。
RandomAccessFile 允许自由定义文件记录指针,RandomAccessFile 可以不从开始的地方开始输出,因此 RandomAccessFile 可以向已存在的文件后追加内容。如果程序需要向已存在的文件后追加内容,则应该使用 RandomAccessFile。
RandomAccessFile 的方法虽然多,但它有一个最大的局限,就是只能读写文件,不能读写其他 IO 节点。
RandomAccessFile 的一个重要使用场景就是网络请求中的多线程下载及断点续传。
关于字节
计算机中的所有数据都是由 0、1 组成,每 8 位为 1 个字节,读写数据的时候都是按字节读写的。
每 4 位可以由一个十六进制字符表示,如
10010110 00010101 11111001
96 15 f9
所以每两个十六进制字符就可以表示 1 个字节。
abc 这三个字母在计算机中的存储是这样的:
a 在计算机中存储的十进制值是 97,二进制为 01100001, 十六进制为 61
b 十进制 98,二进制为 01100010,十六进制为 62
c 十进制 99,二进制为 01100011,十六进制为 63
RandomAccessFile 使用
创建对象
RandomAccessFile raf = new RandomAccessFile(文件,r);
RandomAccessFile raf = new RandomAccessFile(文件,rw);
复制代码
这个文件可以是字符串d:/abc
,也可以是封装好的File对象d:/abc
r 为只读,rw 为读写。读文件是输入,读入内存。写文件是输出,都是相对内存来说的。
写方法
write(int b)
int 四个字节中,只输出末尾的一个字节值:[1][2][3][4]->[4]
适用于单字节范围内的值,切掉前边3个不会有数据损失
write(byte[] buff)
输出数组中全部字节值
write(byte[] buff,int from,int length)
输出数组中从 from 开始的 length 个
栗子1:写入练习
public class Main {
public static void main(String[] args) throws Exception {
/*
* 1、如果文件不存在,新建文件
* 2、如果目录不存在,出现异常
*/
RandomAccessFile raf = new RandomAccessFile("d:/abc/f2", "rw");
raf.write(97);
raf.write(98);
raf.write(99);
raf.write(356);
byte[] buff = {
101, 102, 103, 104, 105,
106, 107, 108, 109, 110
};
raf.write(buff);
raf.write(buff, 6, 3);
raf.close();//释放系统资源
}
}
复制代码
运行结果:
如果 D 盘下没有 abc 文件会报错
D 盘下新建 abc 文件夹后再次运行程序,会在 d:/abc 下生成一个 f2 文件
上边介绍中说过,a 在计算机中存储的值是 97(十进制),二进制为 01100001,每四字节可用一个十六进制表示,所以十六进制为 61。
97、98、99 分别转为十六进制为 61、62、63
356 二进制为 0001 0110 0100,十六进制为 164,补全 8 位为 00 00 01 64,由于write(int b)
方法每四字节只输出最后一个字节,所以只输出了 64。
只要不超过 255 就不会有数据损失,97 十六进制 00 00 00 61,只输出最后 61,没有数据损失。
然后输出数组 buff,raf.write(buff)
方法可以把数组全部输出,数组中 10 个数字都转为十六进制:
101–>65
…
110–>6e
raf.write(buff,6,3)
从数组下标 6 开始,输出 3 个,也就是 107、108、109。分别转为十六进制:
107–>6b
108–>6c
109–>6d
raf.close()
方法通知虚拟机把底层资源释放掉,虚拟机再通知操作系统把系统资源释放掉,之后不能再向文件输出数据了。
读取方法
read();
读取一个字节值,前补 3 个0,转成 int,如
10001010 11000011 11110111 01110001
10001010 将被读取出来,然后前边补 3 个 0,也就是:
00000000 00000000 00000000 10001010
读取结束再读取会返回 -1。
read(byte[] buff);
根据数组容量,读取一批字节值放入数组。返回这一批的字节数量,字节数量不一定是数组容量。
如果读取结束,返回-1
栗子2:单字节读取
我们修改上面的代码,在raf.close();
这行之前增加以下代码
//读取
raf.seek(0);//定位下标
//单字节读取标准格式
int b;
while ((b = raf.read()) != -1) {
System.out.println(b);
}
//随着读取,下标也会向后移动
raf.seek(0);
raf.close();//释放系统资源
复制代码
输出结果
栗子3:批量读取
我们还是修改栗子1,在raf.close();
这行之前增加以下代码
//随着读取,下标也会向后移动,已经移动到末尾
raf.seek(0);
//批量读取标准格式
buff = new byte[5];
int n;//保存每批数量
while ((n = raf.read(buff)) != -1) {
System.out.println(
n + "-" + Arrays.toString(buff));
}
raf.close();//释放系统资源
复制代码
输出结果
操作记录指针方法
void seek(long pos)
将文件指针定位到 pos 位置,然后下次读文件数据的时候从该位置读取。
getFilePointer()
获得下标当前位置
当程序新创建一个 RandomAccessFile 对象时,该对象的文件指针记录位于文件头(也就是0处),当读/写了 n 个字节后,文件记录指针将会后移 n 个字节。除此之外,RandomAccessFile 还可以自由移动该记录指针。
栗子4:文件加密解密,单字节实现
public class Main {
public static void main(String[] args) {
System.out.println("请输入文件路径");
String s = new Scanner(System.in).nextLine();
File f = new File(s);
if (!(f.isFile())) {
System.out.println("请输入正确的文件路径");
return;
}
System.out.println("KEY:");
int key = new Scanner(System.in).nextInt();
try {
encript(f, key);
System.out.println("加密/解密完成");
} catch (Exception e) {
System.out.println("加密/解密失败");
e.printStackTrace();
}
}
private static void encript(File f, int key) throws Exception {
/*
*1、新建 RandomAccessFile 赋值给 raf
*2、单字节循环读取,读取的字节值赋值给 b
* 3、b异或 k,结果再赋给 b
* 4、定位下标到 raf.getFilePointer()-1
* 5、将字节值 b写回到文件
*6、关闭 raf
*/
RandomAccessFile raf = new RandomAccessFile(f, "rw");
int b;
while ((b = raf.read()) != -1) {
b ^= key;
raf.seek(raf.getFilePointer() - 1);
raf.write(b);
}
raf.close();
}
}
复制代码
我们用 d:/abc/f2 文件来完成加密和解密,加密之前内容
运行时如果输入错误的文件路径会进行提示
输入正确的文件路径时:
加密后的文件内容为
再次执行还原为原来的内容
栗子5:文件加密解密,多字节实现
单字节实现效率低,我们改用多字节实现,修改加密解密方法:
private static void encript(File f, int key) throws Exception {
RandomAccessFile raf = new RandomAccessFile(f, "rw");
byte[] buff = new byte[8192];
int n;//保存每一批的数量
while ((n = raf.read(buff)) != -1) {
//前边都是8192,最后一批可能不是8192
for (int i = 0; i < n; i++) {
buff[i] ^= key;
}
raf.seek(raf.getFilePointer() - n);
//不用全部写回,因为最后一批可能只有一部分
raf.write(buff, 0, n);
}
}
复制代码
其他方法
writeInt(int i)
输出 int 的4个字节值,完整输出
writeDouble(double d)
输出 double 的 8 个字节值,完整输出… writeFloat 等都有对应的方法。
writeUTF(String s)
先输出 2 个字节,表示字符串的字节长度,再输出相应字符串的字节值,例如 “abc” 会这样输出 00 03 61 62 63
readInt()
读 4 个字节转成 int
readDouble()
读 8 个字节转成 double
…
readUTF()
先读取两个字节,来确定字符串的字节长度,再读取字节值转成 String
如果读取结束再读取,出现 EOFException(EOF-End of file)
栗子6:其他方法练习
public static void main(String[] args) throws Exception {//选择父类型
RandomAccessFile raf = new RandomAccessFile("d:/abc/f3", "rw");
raf.write(97);
raf.write(98);
raf.write(99);
raf.writeInt(97);
raf.writeInt(98);
raf.writeInt(99);
raf.writeShort(100);
raf.writeBoolean(false);
raf.writeChar('中');
raf.writeUTF("abc中文");
raf.writeDouble(3.14);
raf.close();
}
复制代码
运行结程序会在 D 盘 abc 文件夹下生成 f3 文件,内容使用十六进制查看:
来分析下程序:
raf.write(97);
raf.write(98);
raf.write(99);
复制代码
输出末尾字节
61 62 63
raf.writeInt(97);
raf.writeInt(98);
raf.writeInt(99);
复制代码
输出完整 4 个字节
00 00 00 61
00 00 00 62
00 00 00 63
复制代码
raf.writeShort(100);
复制代码
short 两个字节,所以输出
00 64
raf.writeBoolean(false);
复制代码
布尔值只有 1 个字节,false 是 0
所以输出
00
raf.writeChar('中');
复制代码
char 两个字节,所以输出
4E 2D
raf.writeUTF("abc中文");
复制代码
00 09 9个字节
61 62 63 E4 B8 AD E6 96 87
raf.writeDouble(3.14);
复制代码
double 8个字节
40 09 1E B8 51 EB 85 1F
栗子7:文件读取
修改上面的代码,在 raf.close();
之前增加以下代码:
//文件读取
raf.seek(0);
System.out.println(raf.read());
System.out.println(raf.read());
System.out.println(raf.read());
System.out.println(raf.readInt());
System.out.println(raf.readInt());
System.out.println(raf.readInt());
System.out.println(raf.readShort());
System.out.println(raf.readBoolean());
System.out.println(raf.readChar());
System.out.println(raf.readUTF());
System.out.println(raf.readDouble());
raf.close();
复制代码
运行结果
最后需要注意,当 RandomAccessFile 向指定文件中插入内容时,将会覆盖掉原有内容。如果不想覆盖掉,则需要将原有内容先读取出来,然后先把插入内容插入后再把原有内容追加到插入内容后。