一、RandomAccessFile简介
RandomAccessFile既可以读取文件,也可以写文件。顾名思义,RandomAccessFile支持“随机访问”的方式读写文件,这里的随机访问是指,指定任何一个位置,都能够访问它。
由于RandomAccessFile可以自由访问文件的任意位置,所以如果需要读写文件的部分内容,不需要把文件从头读到尾从头写到尾,使用RandomAccessFile极大的方便了文件操作。
RandomAccessFile的一个重要使用场景就是网络请求中的多线程下载及断点续传。
2.RandomAccessFile的重要方法
RandomAccessFile对象包含了一个记录指针,用以标识当前读写处的位置,当创建一个RandomAccessFile对象时,该对象的文件指针记录位于文件头(也就是0处),当读/写了n个字节后,文件记录指针将会后移n个字节。除此之外,RandomAccessFile还可以自由移动该记录指针。
下面就是RandomAccessFile具有的两个特殊方法,来操作记录指针,实现随机访问:
long getFilePointer( ):返回文件记录指针的当前位置
void seek(long pos ):将文件指针定位到pos位置
三、RandomAccessFile的使用
利用RandomAccessFile实现文件的多线程下载,每个线程从源文件下载一部分,最后写到同一个文件,下面是一个利用多线程下载文件的例子。
import org.springframework.util.StopWatch;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
public class Test {
public static void main(String[] args) throws Exception {
// 等待文件下载文件,统计下载耗时
CountDownLatch latch = new CountDownLatch(5);
StopWatch stopWatch = new StopWatch();
stopWatch.start("下载文件");
RandomAccessFile sourceFile = new RandomAccessFile("e://signdriver.zip", "r");
RandomAccessFile destFile = new RandomAccessFile("e://signdriver_out.zip", "rw");
Writer writer = new Writer(sourceFile, destFile);
for (int i = 0; i < 10; ++i) {
new FileWriteThread(writer, latch).start();
}
latch.await();
stopWatch.stop();
System.out.println("下载文件耗时:" + stopWatch.getTotalTimeMillis() + "ms");
}
}
class Writer {
private RandomAccessFile sourceFile;
private RandomAccessFile destFile;
private int position;
public Writer(RandomAccessFile sourceFile, RandomAccessFile destFile) {
this.sourceFile = sourceFile;
this.destFile = destFile;
this.position = 0;
}
private void writeFile(byte[] bytes, int position, int readLength) throws IOException {
destFile.seek(position);
destFile.write(Arrays.copyOf(bytes, readLength));
}
public synchronized int doWrite() throws IOException {
byte[] bytes = new byte[10240];
int readLength;
if ((readLength = sourceFile.read(bytes)) != -1) {
System.out.println(Thread.currentThread().getName() + "读取了" + readLength + "字节");
writeFile(bytes, position, readLength);
position += readLength;
sourceFile.seek(position);
return bytes.length;
} else {
return -1;
}
}
public void close() {
try {
if (sourceFile != null) {
sourceFile.close();
}
if (destFile != null) {
destFile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class FileWriteThread extends Thread {
private Writer writer;
private CountDownLatch latch;
FileWriteThread(Writer writer, CountDownLatch latch) {
this.writer = writer;
this.latch = latch;
}
@Override
public void run() {
try {
while (true) {
int length = writer.doWrite();
if (length == -1) {
System.out.println("文件下载完毕,线程退出");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
writer.close();
latch.countDown();
}
}
}
笔者暂时没找到能模拟远程下载的场景,就在自己电脑随便找了个文件做读写测试,开启10个线程并发读写,90多兆的文件下载完仅需397ms。
耗时统计:
代码仅仅实现基本的多线程读写功能,还有很多能优化的地方,比如读写文件可以用nio,读写文件加锁的优化,大家有兴趣可以直接copy上面的代码做修改。
END