BIO中RandomAccessFile的使用

2020-04-14  本文已影响0人  文景大大

以前我们对文件的读写都使用什么呢?

如果是字节流,我们有FileInputStream和FileOutputStream;如果是字符流我们有FileReader和FileWriter;

然而,本文的主角RandomAccessFile可以同时支持对文件的读写,并且可以支持随机访问文件,即访问文件的任意位置内容。如此,我们可以使用多线程的方式来对同一个文件进行读写操作,大大提高了IO的效率。

首先我们来看一个实例,来熟悉一下RandomAccessFile的使用:

test.txt

123456789

Test.java

    public static void main(String[] args) throws IOException {
        // r-只读模式;rw-读写模式
        RandomAccessFile raf = new RandomAccessFile("test.txt","rw");
        log.info("文件中总字节的长度为:{}", raf.length());
        log.info("当前读写指针的位置为:{}", raf.getFilePointer());

        // 重置指针到指定位置
        raf.seek(3);
        log.info("当前读写指针的位置为:{}", raf.getFilePointer());
        raf.close();
    }

我们可以通过指定模式来实例化RandomAccessFile对象,这里先只了解最常用的两种模式即可。

seek的作用在于可以重置读写指针的位置,这是一个非常强大的功能,我们再来看一个例子:

    public static void main(String[] args) throws IOException {
        // r-只读模式;rw-读写模式
        RandomAccessFile raf = new RandomAccessFile("test.txt","rw");
        // 在首部插入hello
        raf.write("hello".getBytes());
        // 重置指针到hello之后
        raf.seek(5);
        raf.writeBytes(",world");
        raf.close();
    }

此时,test.txt中的内容就变为:

hello,world

我们再来一个多线程复制文件的例子:

CopyFileThread.java

@Slf4j
public class CopyFileThread implements Runnable {
    private RandomAccessFile inFile;
    private RandomAccessFile outFile;
    private Long start;
    private Long end;
    private byte[] readBytes;
    private int readIndex;

    public CopyFileThread(String inFile, String outFile, Long start, Long end) throws FileNotFoundException {
        this.start = start;
        this.end = end;
        this.inFile = new RandomAccessFile(inFile, "rw");
        this.outFile = new RandomAccessFile(outFile, "rw");
        // 默认设置一个3字节数组,用来存放每次复制时读取的内容
        this.readBytes = new byte[3];
        // 每次复制时读取的字节数
        this.readIndex = 0;
    }

    @Override
    public void run() {
        try {
            // 定位到当前线程分工需要开始读取的地方
            inFile.seek(start);
            // 定位到当前线程分工需要开始写出的地方
            outFile.seek(start);
            // 当前线程分工范围内,且文件未读取完时循环读取
            while(start < end && readIndex != -1){
                // 防止当前线程越界读取别的线程所负责范围内的字节
                if((end - start) >= readBytes.length){
                    // 读取readBytes大小的字节
                    this.readIndex = inFile.read(readBytes);
                } else {
                    // 读取剩余应该读取内容的字节数,此时end-start肯定小于readBytes.length,可以放心地强转
                    this.readIndex = inFile.read(readBytes, 0, (int)(end - start));
                }
                // 将读到的数据从头到尾进行写出
                outFile.write(readBytes, 0, readIndex);
                start += readIndex;
            }
        } catch (IOException e) {
            log.info("文件复制出现异常:", e);
        } finally {
            try {
                inFile.close();
                outFile.close();
            } catch (IOException e) {
                log.info("随机文件流关闭出现异常:", e);
            }
        }
    }
}

Test.java

@Slf4j
public class Test {
    private static final String IN_FILE = "test1.txt";
    private static final String OUT_FILE = "test2.txt";
    private static final Integer THREAD_COUNT = 2;

    public static void main(String[] args) throws IOException {
        startCopyFile(IN_FILE, OUT_FILE, THREAD_COUNT);
    }

    private static void startCopyFile(String inFile, String outFile, int threadCount) throws FileNotFoundException {
        // 每个线程需要负责拷贝的字节长度
        long segmentFileLength = new File(IN_FILE).length() / threadCount;
        for (int i = 0; i < threadCount; i++) {
            // 为每个线程进行分工
            new Thread(new CopyFileThread(IN_FILE, OUT_FILE, i*segmentFileLength, (i+1)*segmentFileLength)).start();
        }
    }
}

关于多线程如何调试,可以参考这篇文章《IDEA调试技巧进阶》

上一篇下一篇

猜你喜欢

热点阅读