RandomAccessFile多线程下载、复制文件、超大文件读写

简介

最近在准备面试,翻了翻自己以前写的 demo,发现自己写了不少的工具包,今天整理了一下,分享给大家。

本文包含以下demo:

  1. 常用方法测试
  2. 在文件中间插入一段新的数据
  3. 多线程下载文件
  4. 多线程复制文件
  5. 超大文件的读写

前置技能:掌握 IO 相关的的基本语法

具体注意事项,我会全部写在注释中,它跟普通文件读写类似,不过就是多了一个游标,可以从文件指定位置开始读取。

留言

时间一晃,这都是自己八年前的代码了,这些代码确实没啥用。

多线程下载,是以空间换时间的做法,大量消耗内存和CPU,将资源全部拿去下载文件。

但是,受到网络带宽的影响,下载的性能,通常达不到理论最大值。

而且,要开多少个线程合适?如果网络中断,该如何处理?后期代码维护成本非常大。

除非实际有这方面需求,并且已经做好长期维护的准备,否则不要使用。

RandomAccessFile常用方法


/**
 * 这个Demo是刚开始接触RandomAccessFile从网络上找到的,如今我也不记得出处
 * 
 * @author ?? 2016/11/7
 * @version 1.0
 */

import java.io.RandomAccessFile;

public class RandomAccessFileDemo {
    public static void main(String[] args) throws Exception {
        RandomAccessFile file = new RandomAccessFile("file", "rw");
        // 占4个字节
        file.writeInt(20);
        // 占8个字节
        file.writeDouble(8.236598);
        // 这个长度写在当前文件指针的前两个字节处,可用readShort()读取
        file.writeUTF("这是一个UTF字符串");
        // 占1个字节
        file.writeBoolean(true);
        // 占2个字节
        file.writeShort(395);
        // 占8个字节
        file.writeLong(2325451l);
        file.writeUTF("又是一个UTF字符串");
        // 占4个字节
        file.writeFloat(35.5f);
        // 占2个字节
        file.writeChar('a');
        // 把文件指针位置设置到文件起始处
        file.seek(0);

        // 以下从file文件中读数据,要注意文件指针的位置
        System.out.println("——————从file文件指定位置读数据——————");
        System.out.println(file.readInt());
        System.out.println(file.readDouble());
        System.out.println(file.readUTF());

        // 将文件指针跳过3个字节,本例中即跳过了一个boolean值和short值。
        file.skipBytes(3);
        System.out.println(file.readLong());

        // 跳过文件中“又是一个UTF字符串”所占字节,注意readShort()方法会移动文件指针,所以不用加2。
        file.skipBytes(file.readShort());
        System.out.println(file.readFloat());

        // 以下演示文件复制操作
        System.out.println("——————文件复制(从file到fileCopy)——————");
        file.seek(0);
        RandomAccessFile fileCopy = new RandomAccessFile("fileCopy", "rw");
        // 取得文件长度(字节数)
        int len = (int) file.length();
        byte[] b = new byte[len];
        file.readFully(b);
        fileCopy.write(b);
        System.out.println("复制完成!");
        file.close();
        fileCopy.close();
    }
}

超大文件读写

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

/**
 * 超大文件读写
 * 
 * @author ChenSS 2016/11/7
 * @version 1.0
 */
public class LargeMappedFiles {
    static int length = 0x8000000; // 128 Mb

    public static void main(String[] args) throws Exception {
        // 为了以可读可写的方式打开文件,这里使用RandomAccessFile来创建文件。
        // 注意,文件通道的可读可写要建立在文件流本身可读写的基础之上
        RandomAccessFile accessFile = new RandomAccessFile("test.dat", "rw");
        FileChannel fileChannel = accessFile.getChannel();

        // 使用MappedByteBuffer写128M的内容
        MappedByteBuffer out = fileChannel.map(FileChannel.MapMode.READ_WRITE,
                0, length);
        for (int i = 0; i < length; i++) {
            out.put((byte) 'x');
        }

        // 另一种写入方式,128M结尾追加一句话,待会再想办法读出来
        String newData = "这是最后一句话";
        // 分配字节缓冲区
        ByteBuffer buf = ByteBuffer.allocate(48);
        // clear方法将缓冲区清空。
        buf.clear();
        // 放入字符串
        buf.put(newData.getBytes());
        // 回到当前缓存的头部
        buf.flip();
        // hasRemaining告知在当前位置和限制之间是否有元素。
        while (buf.hasRemaining()) {
            fileChannel.write(buf);
        }

        System.out.println("========================");

        StringBuilder result = new StringBuilder();
        // 定义字节缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // 定义解码后字符存储缓冲区
        CharBuffer charBuffer = CharBuffer.allocate(1024);
        // 定义合适的字符集解码器
        CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();

        // 设置此缓冲区的位置
        fileChannel.position(length);
        while ((fileChannel.read(byteBuffer)) != -1) { // 读取字符串到缓冲区
            byteBuffer.flip();
            charBuffer.clear();
            // 对byteBuffer进行解码
            if (fileChannel.position() < fileChannel.size()) {
                decoder.decode(byteBuffer, charBuffer, false);
            } else {
                // 最后一次解码
                decoder.decode(byteBuffer, charBuffer, true);
                decoder.flush(charBuffer);
            }
            // 注意此处调用compact方法,而不是clear方法
            byteBuffer.compact();
            charBuffer.flip();
            // 将charBuffer放入返回结果中
            char[] chars = new char[charBuffer.remaining()];
            charBuffer.get(chars, 0, charBuffer.remaining());
            result.append(chars);
        }
        System.out.println(result);

        // 读取文件中间6个字节内容
        for (int i = length / 2; i < length / 2 + 6; i++) {
            System.out.print((char) out.get(i));
        }
        fileChannel.close();
        accessFile.close();
    }
}

文件追加内容

/**
 * 文件追加
 * @author ChenSS 2016/11/7
 * @version 1.0
 */
public class FileAppend {
    public static void main(String[] args) {
        FileAppend.append(4, "zhuangjiangtao", "file.txt");
    }

    public static void append(long skip, String str, String fileName) {
        try {
            RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
            byte[] b = str.getBytes();
            long len = b.length;
            long start = len + skip;
            // 重新开辟空间
            raf.setLength(raf.length() + len);
            for (long i = raf.length() - 1; i >= start; i--) {
                raf.seek(i - len);
                byte temp = raf.readByte();
                raf.seek(i);
                raf.writeByte(temp);
            }
            raf.seek(skip);
            raf.write(b);
            raf.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

多线程复制文件

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
/**
 * 多线程复制文件
 * @author ChenSS 2016/11/7
 * @version 1.0
 */
public class UsingThreadRandom {
    public static void main(String[] args) throws Exception {
        File file = new File("aaa.jpg");
        startThread(4, file.length(), "aaa.jpg", "baabb.jpg");
    }

    /**
     * 开启多线程下载
     * 
     * @param threadnum 线程数
     * @param fileLength 文件大小(用于确认每个线程下载多少东西)
     * @param sourseFilePath 源文件目录
     * @param targerFilePath 目标文件目录
     */
    public static void startThread(int threadnum, long fileLength,
            String sourseFilePath, String targerFilePath) {
        System.out.println("================");
        long modLength = fileLength % threadnum;
        long targetLength = fileLength / threadnum;
        for (int i = 0; i < threadnum; i++) {
            System.out.println((targetLength * i) + "-----"
                    + (targetLength * (i + 1)));
            new FileWriteThread((targetLength * i), (targetLength * (i + 1)),
                    sourseFilePath, targerFilePath).start();
        }
        if (modLength != 0) {
            new FileWriteThread((targetLength * 4), modLength, sourseFilePath,
                    targerFilePath).start();
        }
    }


    /**
     * 写线程:指定文件开始位置、目标位置、源文件、目标文件,  
     */
    static class FileWriteThread extends Thread {
        private long begin;
        private long end;
        private RandomAccessFile soursefile;
        private RandomAccessFile targerFile;

        public FileWriteThread(long begin, long end, String sourseFilePath,
                String targerFilePath) {
            this.begin = begin;
            this.end = end;
            try {
                this.soursefile = new RandomAccessFile(sourseFilePath, "rw");
                this.targerFile = new RandomAccessFile(targerFilePath, "rw");
            } catch (FileNotFoundException e) {
            }
        }

        public void run() {
            try {
                soursefile.seek(begin);
                targerFile.seek(begin);
                int hasRead = 0;
                byte[] buffer = new byte[1024];
                while (begin < end && -1 != (hasRead = soursefile.read(buffer))) {
                    begin += hasRead;
                    targerFile.write(buffer, 0, hasRead);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    soursefile.close();
                    targerFile.close();
                } catch (Exception e) {
                }
            }
        }
    }
}

多线程下载文件

测试方法

import java.io.File;

/**
 * 
 * @author ChenSS 2016/11/7
 * @version 1.0
 */
public class Test {
    private static final int NUMBER = 10;
    // public static final String URL_DOWNLOAD =
    // "http://www.swsm.net/data/attachment/forum/201506/19/171831uzzeejjxke1jkgme.jpg";
    public static final String URL_DOWNLOAD = "https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png";
    public static final String PATH_TARGET = "F:/temp/download/";

    public static void main(String[] args) {
        Loader loder = new Loader();
        File file = loder.createFile(PATH_TARGET, URL_DOWNLOAD);
        loder.startLoadThread(NUMBER, file, URL_DOWNLOAD);
    }
}

开线程,如何分线程代码逻辑

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;

/**
 * 
 * @author ChenSS 2016/11/7
 * @version 1.0
 */
public class Loader {
    /**
     * 创建目标文件(希望保存的文件和下载的同名)
     * 
     * @param targetPath
     *            目标路径
     * @param sourseURL
     *            根据源URL获取文件名
     * @return
     */
    public File createFile(String targetPath, String sourseURL) {
        return new File(targetPath
                + sourseURL.substring(sourseURL.lastIndexOf("/") + 1));
    }

    /**
     * 如果出现不整除的情况(如:11字节,4个线程,每个线程3字节,多出1字节),但是实际上RandomAccessFile的read()
     * 读到文件尾会返回-1,因此不考虑余数问题
     * 
     * @param threadNum
     *            线程数量
     * @param targetFile
     *            目标文件
     * @param sourseURL
     *            源文件URL
     */
    public void startLoadThread(int threadNum, File targetFile, String sourseURL) {
        try {
            // 网络连接
            URLConnection connection = new URL(sourseURL).openConnection();
            long sourseSize = connection.getContentLengthLong();
            // 为目标文件分配空间
            this.openSpace(targetFile, sourseSize);
            // 分线程下载文件
            long avgSize = sourseSize / threadNum + 1;
            for (int i = 0; i < threadNum; i++) {
                System.out
                        .println(avgSize * i + "------" + (avgSize * (i + 1)));
                new Thread(new DownloadsTask(avgSize * i, avgSize * (i + 1),
                        targetFile, sourseURL)).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 为目标文件分配空间
     * 
     * @param targetfile
     *            目标文件
     * @param sourseSize
     *            源文件大小
     */
    private void openSpace(File targetfile, Long sourseSize) {
        RandomAccessFile randomAccessFile = null;
        try {
            randomAccessFile = new RandomAccessFile(targetfile, "rw");
            randomAccessFile.setLength(sourseSize);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (randomAccessFile != null)
                    randomAccessFile.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

实现Runnable,线程下载逻辑

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;

/**
 * 
 * @author ChenSS 2016/11/7
 * @version 1.0
 */
public class DownloadsTask implements Runnable {
    private long start;
    private long end;
    private File file;
    private String loadUrl;

    /**
     * 构造函数
     * 
     * @param start
     *            开始位置
     * @param end
     *            结束位置
     * @param targetFile
     *            目标文件
     * @param loadUrl
     *            下载网址
     */
    public DownloadsTask(long start, long end, File targetFile, String loadUrl) {
        this.start = start;
        this.end = end;
        this.file = targetFile;
        this.loadUrl = loadUrl;
    }

    @Override
    public void run() {
        BufferedInputStream bufferedInputStream = null;
        RandomAccessFile randomAccessFile = null;
        try {
            URL url = new URL(loadUrl);
            URLConnection conn = url.openConnection();
            bufferedInputStream = new BufferedInputStream(conn.getInputStream());
            randomAccessFile = new RandomAccessFile(file, "rw");

            // 源文件和目标文件的指针指向同一个位置
            bufferedInputStream.skip(start);
            randomAccessFile.seek(start);

            long readLen = end - start;
            // 如果比默认长度小,就没必要按照默认长度读取文件了
            byte[] bs = new byte[(int) (2048 < readLen ? 2048 : readLen)];
            while (start < end
                    && (readLen = bufferedInputStream.read(bs)) != -1) {
                start += readLen;
                randomAccessFile.write(bs, 0, (int) readLen);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭流
            try {
                if (null != bufferedInputStream)
                    bufferedInputStream.close();
                if (null != randomAccessFile)
                    randomAccessFile.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

posted on 2016-11-07 21:47  疯狂的妞妞  阅读(1403)  评论(0编辑  收藏  举报

导航