15.7 RandomAccessFile

15.7 RandomAccessFile

RandomAccessFile可读可写

RandomAccessFileJava输入输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,它既可以读取文件内容,也可以向文件输出数据

随机访问

与普通的输入/输出流不同的是,RandomAccessFile支持“随机访问”的方式,程序可以直接跳转到文件的任意地方来读写数据
由于RandomAccessFile可以自由访问文件的任意位置,所以如果只需要访问文件部分内容,而不是把文件从头读到尾,使用RandomAccessFile将是更好的选择
OutputStreamWriter等输出流不同的是,RandomAccessFile允许自由定位文件记录指针,RandomAccessFile可以不从开始的地方开始输出,因此RandomAccessFile可以向已存在的文件后追加内容。如果程序需要向已存在的文件后追加内容,则应该使用RandomAccessFile

只能读写文件

RandomAccessFile的方法虽然多,但它有一个最大的局限,就是只能读写文件,不能读写其他IO节点

文件记录指针

RandomAccessFile对象也包含了一个记录指针,用以标识当前读写处的位置,当程序新创建一个RandomAccessFile对象时,该对象的文件记录指针位于文件头(也就是0处),当读写了n个字节后,文件记录指针将会向后移动n个字节。

方法

移动记录指针的方法

除此之外,RandomAccessFile可以自由移动该记录指针,既可以向前移动,也可以向后移动。RandomAccessFile包含了如下两个方法来操作文件记录指针。

方法 描述
long getFilePointer() 返回文件记录指针的当前位置。
void seek(long pos) 将文件记录指针定位到pos位置

read方法

RandomAccessFile既可以读文件,也可以写,所以它既包含了完全类似于InputStream的三个read()方法,其用法和InputStream的三个read()方法完全一样;
也包含了完全类似于OutputStream的三个write方法,其用法和OutputStream的三个write方法完全一样。
除此之外,RandomAccessFile还包含了系列的readXxx()writeXxx()方法来完成输入、输出。

方法 描述
int read() Reads a byte of data from this file.
int read(byte[] b) Reads up to b.length bytes of data from this file into an array of bytes.
int read(byte[] b, int off, int len) Reads up to len bytes of data from this file into an array of bytes.

write方法

方法 描述
void write(int b) Writes the specified byte to this file.
void write(byte[] b) Writes b.length bytes from the specified byte array to this file, starting at the current file pointer.
void write(byte[] b, int off, int len) Writes len bytes from the specified byte array starting at offset off to this file.

readXxx方法

方法 描述
boolean readBoolean() Reads a boolean from this file.
byte readByte() Reads a signed eight-bit value from this file.
char readChar() Reads a character from this file.
double readDouble() Reads a double from this file.
float readFloat() Reads a float from this file.
void readFully(byte[] b) Reads b.length bytes from this file into the byte array, starting at the current file pointer.
void readFully(byte[] b, int off, int len) Reads exactly len bytes from this file into the byte array, starting at the current file pointer.
int readInt() Reads a signed 32-bit integer from this file.
String readLine() Reads the next line of text from this file.
long readLong() Reads a signed 64-bit integer from this file.
short readShort() Reads a signed 16-bit number from this file.
int readUnsignedByte() Reads an unsigned eight-bit number from this file.
int readUnsignedShort() Reads an unsigned 16-bit number from this file.
String readUTF() Reads in a string from this file.

writeXxx方法

方法 描述
void writeBoolean(boolean v) Writes a boolean to the file as a one-byte value.
void writeByte(int v) Writes a byte to the file as a one-byte value.
void writeBytes(String s) Writes the string to the file as a sequence of bytes.
void writeChar(int v) Writes a char to the file as a two-byte value, high byte first.
void writeChars(String s) Writes a string to the file as a sequence of characters.
void writeDouble(double v) Converts the double argument to a long using the doubleToLongBits method in class Double, and then writes that long value to the file as an eight-byte quantity, high byte first.
void writeFloat(float v) Converts the float argument to an int using the floatToIntBits method in class Float, and then writes that int value to the file as a four-byte quantity, high byte first.
void writeInt(int v) Writes an int to the file as four bytes, high byte first.
void writeLong(long v) Writes a long to the file as eight bytes, high byte first.
void writeShort(int v) Writes a short to the file as two bytes, high byte first.
void writeUTF(String str) Writes a string to the file using modified UTF-8 encoding in a machine-independent manner.

其他方法

方法 描述
void close() Closes this random access file stream and releases any system resources associated with the stream.
FileChannel getChannel() Returns the unique FileChannel object associated with this file.
FileDescriptor getFD() Returns the opaque file descriptor object associated with this stream.
long length() Returns the length of this file.
void setLength(long newLength) Sets the length of this file.
int skipBytes(int n) Attempts to skip over n bytes of input discarding the skipped bytes.

随机访问翻译不对

RandomAccessFile的含义是可以自由访问文件的任意地方(与InputStreamReader需要依次向后读取相区分),所以Random Access File的含义决不是“随机访问”,而应该是“任意访问”。

构造器

RandomAccessFile类有两个构造器,其实这两个构造器基本相同,只是指定文件的形式不同而已个使用String参数来指定文件名,一个使用File参数来指定文件本身。

方法 描述
RandomAccessFile(File file, String mode) Creates a random access file stream to read from, and optionally to write to, the file specified by the File argument.
RandomAccessFile(String name, String mode) Creates a random access file stream to read from, and optionally to write to, a file with the specified name.

mode参数

除此之外,创建RandomAccessFile对象时还需要指定一个mode参数,该参数指定RandomAccessfile的访问模式,该参数有如下4个值。

mode参数值 意义
“r” 只读方式打开指定文件。如果试图对该RandomAccessFile执行写入方法,都将抛出IOException异常
“rw” 读、写方式打开指定文件。如果该文件尚不存在,则尝试创建该文件。
“rwd” 读、写方式打开指定文件。相对于”rw“模式,还要求对文件内容的每个更新都同步写入到底层存储设备
“rws” 读、写方式打开指定文件。相对于”rw“模式,还要求对文件的内容元数据的每个更新都同步写入到底层存储设备

程序 从文件中间读取

下面程序使用了RandomAccessFile来访问指定的中间部分数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.*;

public class RandomAccessFileTest {
public static void main(String[] args) {
try (RandomAccessFile raf = new RandomAccessFile("RandomAccessFileTest.java", "r")// 代码1
) {
// 获取RandomAccessFile对象文件指针的位置,初始位置是0
System.out.println("RandomAccessFile的文件指针的初始位置:" + raf.getFilePointer());
// 移动raf的文件记录指针的位置
raf.seek(300);// 代码2
byte[] bbuf = new byte[1024];
// 用于保存实际读取的字节数
int hasRead = 0;
// 使用循环来重复“取水”过程
while ((hasRead = raf.read(bbuf)) > 0) {
// 取出“竹筒”中水滴(字节),将字节数组转换成字符串输入!
System.out.print(new String(bbuf, 0, hasRead));
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

上面程序中的代码1创建了一个RandomAccessFile对象,该对象以只读方式打开了RandomAccessFileTest.java文件,这意味着该RandomAccessFile对象只能读取文件内容,不能执行写入。
程序中代码2将文件记录指针定位到300处,也就是说,程序将从300字节处开始读、写,程序接下来的部分与使用InputStream读取并没有太大的区别
运行上面程序,将看到程序只读取后面部分的效果。

程序 在文件末尾追加内容

下面程序示范了如何向指定文件后追加内容,为了追加内容,程序应该先将记录指针移动到文件最后,然后开始向文件中输出内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.*;

public class AppendContent {
public static void main(String[] args) {
try (
// 以读、写方式打开一个RandomAccessFile对象
RandomAccessFile raf = new RandomAccessFile("out.txt", "rw")) {
// 将记录指针移动到out.txt文件的最后
raf.seek(raf.length());
raf.write("追加的内容!\r\n".getBytes());
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

上面程序中先以读、写方式创建了一个RandomAccessFile对象,然后将RandomAccessFile对象的记录指针移动到最后;接下来使用RandomAccessfile执行输出,与使用OutputStreamWriter执行输出并没有太大区别。
每运行上面程序一次,都可以看到out.txt件中多一行“追加的内容!”字符串,程序在该字符串后使用“\r\n”是为了控制换行。

在文件中间插入内容

直接在中间位置插入内容 会丢失原来中间之后的内容

RandomAccessFile依然不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置后开始输出,则新输岀的内容会覆盖文件中原有的内容

先缓存中间之后的内容 再插入新内容 最后插入原来中间的内容

如果需要向指定位置插入内容,程序需要先把插入点后面的内容读入缓冲区,等把需要插入的数据写入文件后,再将缓冲区的内容追加到文件后面

程序 在文件中间插入内容

下面程序实现了向指定文件、指定位置插入内容的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.*;

public class InsertContent {
public static void insert(String fileName, long pos, String insertContent) throws IOException {
File tmp = File.createTempFile("tmp", null);
tmp.deleteOnExit();
try (RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
// 使用临时文件来保存插入点后的数据
FileOutputStream tmpOut = new FileOutputStream(tmp);
FileInputStream tmpIn = new FileInputStream(tmp)) {
raf.seek(pos);
// ------下面代码将插入点后的内容读入临时文件中保存------
byte[] bbuf = new byte[64];
// 用于保存实际读取的字节数
int hasRead = 0;
// 使用循环方式读取插入点后的数据
while ((hasRead = raf.read(bbuf)) > 0) {
// 将读取的数据写入临时文件
tmpOut.write(bbuf, 0, hasRead);
}
// ----------下面代码插入内容----------
// 把文件记录指针重新定位到pos位置
raf.seek(pos);
// 追加需要插入的内容,会覆盖插入点后的内容
raf.write(insertContent.getBytes());
// 重新读入被覆盖的内容,追加临时文件中的内容
while ((hasRead = tmpIn.read(bbuf)) > 0) {
raf.write(bbuf, 0, hasRead);
}
}
}

public static void main(String[] args) throws IOException {
insert("InsertContent.java", 45, "插入的内容\r\n");
}
}

上面程序中使用FilecreateTempFile(String prefix, String suffix)方法创建了一个临时文件(该临时文件将在JVM退出时被删除),用以保存被插入文件的插入点后面的内容。程序先将文件中插入点后的内容读入临时文件中,然后重新定位到插入点,将需要插入的内容添加到文件后面,最后将临时文件的内容添加到文件后面。
这样就可以向指定文件、指定位置插入内容。

实现多线程断点传输工具

多线程断点的网络下载工具(如FlashGet等)就可通过RandomAccessFile类来实现.
所有的下载工具在下载开始时都会建立两个文件:

  • 一个是与被下载文件大小相同的空文件,
  • 一个是记录文件指针的位置文件

下载工具用多条线程启动输入流来读取网络数据,并使用RandomAccessFile将从网络上读取的数据写入前面建立的空文件中,每写一些数据后,记录文件指针的文件就分别记下每个RandomAccessFile当前的文件指针位置——网络断开后,再次开始下载时,每个RandomAccessFile都根据记录文件指针的文件中记录的位置继续向下写数据。

本书将会在介绍多线程和网络知识之后,更加详细地介绍如何开发类似于FlashGet的多线程断点传输工具。