一、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。

java 多线程文件下载后损坏 多线程实现文件下载_读写文件

耗时统计:

java 多线程文件下载后损坏 多线程实现文件下载_java 多线程文件下载后损坏_02

代码仅仅实现基本的多线程读写功能,还有很多能优化的地方,比如读写文件可以用nio,读写文件加锁的优化,大家有兴趣可以直接copy上面的代码做修改。

END