RandomAccessFile多线程下载文件

  • Post author:
  • Post category:其他


在java集合中我们了解到ArrayList 实现的一个接口 RandomAccess,而该接口只是一个标记接口,仅表示支持随机访问。

同理,根据名字可知RandomAccessFile类支持随机读写文件。RandomAccessFile类中比较重要的2个方法,其他的和普通IO类似,在这里,就不详细说明了。

  1. getFilePointer() 返回文件记录指针的当前位置
  2. seek(long pos) 将文件记录指针定位到pos的位置

1、常见使用方式:


1)读取任意位置的数据:

  1. 创建randomAccessFile对象,以读的方式打开文件。RandomAccessFile raf=new RandomAccessFile(path, “r”);
  2. 调用seek(long pos) 方法seek到文件的指定位置(按照字节去seek)。
  3. 调用randomAccessFile对象的read方法读取数据;


2)追加数据:

  1. 创建randomAccessFile对象,以写的方式打开文件。RandomAccessFile raf=new RandomAccessFile(path, “w”);
  2. 调用seek(long pos) 方法seek到文件的尾部(raf.seek(raf.length()); )。
  3. 调用raf的write方法写数据。


3)任意位置插入数据:

如果文件中已经有内容,则:

  1. 创建randomAccessFile对象,以读写的方式打开文件。RandomAccessFile raf=new RandomAccessFile(path, “rw”);
  2. 创建文件缓冲区;
  3. 调用seek(long pos) 方法seek到文件的指定位置,然后把剩下内容写入到缓冲区中;
  4. 调用seek(long pos)方法返回到指定位置,把新内容写入;
  5. 吧缓冲区内容追加到文件尾部。

如果是一个新文件,可以先调用setLength(len)设置文件大小,然后再通过seek(pos)方法在不同位置写入数据。

注:RandomAccessFile需要新建一个缓冲区临时空间,存数据,然后在写,因为一旦数据量上了级别,在任意位置插入数据,是很耗内存的,这个也就是为什么hadoop的HDFS文件系统,只支持append的方式,而没有提供修改的操作。代码如下:

//随机读
public static void randomRed(String path, int pointe) {
    try {
        RandomAccessFile raf = new RandomAccessFile(path, "r"); 
        System.out.println(raf.getFilePointer());//获取RandomAccessFile对象文件指针的位置,初始位置是0
        raf.seek(pointe);// 移动文件指针位置
        byte[] buff = new byte[1024];
        int hasRead = 0;
        while ((hasRead = raf.read(buff)) > 0) {// 循环读取
            System.out.println(new String(buff, 0, hasRead));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
//追加写
public static void randomWrite(String path) {
    try {
        RandomAccessFile raf = new RandomAccessFile(path, "rw");
        raf.seek(raf.length());// 将记录指针移动到文件最后
        raf.write("我是追加的 \r\n".getBytes());
    } catch (Exception e) {
        e.printStackTrace();
    }
}
//实现向指定位置 插入数据
public static void insert(String fileName, long points, String insertContent) {
    try {
        //创建临时文件
        File tmp = File.createTempFile("tmp", null);
        tmp.deleteOnExit();// 在JVM退出时删除

        RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
        raf.seek(points);

        //将插入点后的内容读入临时文件
        FileOutputStream tmpOut = new FileOutputStream(tmp);
        byte[] buff = new byte[1024];
        int hasRead = 0;
        while ((hasRead = raf.read(buff)) > 0) {
            tmpOut.write(buff, 0, hasRead);
        }

        // 追加需要追加的内容
        raf.seek(points);// 返回原来的插入处
        raf.write(insertContent.getBytes());

        // 最后追加临时文件中的内容
        FileInputStream tmpIn = new FileInputStream(tmp);
        while ((hasRead = tmpIn.read(buff)) > 0) {
            raf.write(buff, 0, hasRead);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

2、示例:通过http协议的range方式,实现多线程下载文件


1)在nginx上部署一个文件下载的服务:

使用openresty的docker景象,配置

server {
    listen       80;
    server_name  localhost;

    location = /f1.txt {
        root /data/;
    }
    location = /rt1/2.mp4 {
        root /data/;
    }

创建如下文件:/data/rt1/2.mp4 /data/f1.txt


2)多线程下载:

public class MultiThreadDownload {
    public static String path = "http://127.0.0.1/rt1/2.mp4"; // 要下载的网络资源文件路径
    public static int threadCount = 10; // 开启的线程数
    public static int runningThread = 10; // 记录已经运行的线程数量
    public static long startTime;

    private static final String filePath = "/data/my.mp4"; // 文件存放本地的路径

    public static void main(String[] args) throws Exception {
        startTime = System.currentTimeMillis();
        // 1.连接服务器,获取一个文件,获取文件的长度,在本地创建一个跟服务器一样大小的临时文件
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(5000);
        conn.setRequestMethod("GET");
        int code = conn.getResponseCode();
        if (code == 200) {
            long length = conn.getContentLength();//从http响应头中获取Content-length字段
            System.out.println("文件总长度:" + length);
            // 在客户端本地创建出来一个大小跟服务器端一样大小的临时文件
            RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");
            raf.setLength(length);// 指定创建的这个文件的长度
            raf.close();

            // 平均每一个线程下载的文件大小.
            long blockSize = length / threadCount;
            for (int threadId = 1; threadId <= threadCount; threadId++) {
                long startIndex = (threadId - 1) * blockSize;
                long endIndex = threadId * blockSize - 1;
                if (threadId == threadCount) {// 最后一个线程下载的长度要稍微长一点
                    endIndex = length;
                }
                System.out.println("线程:" + threadId + "下载:---" + startIndex + "--->" + endIndex);
                new DownLoadThread(threadId, startIndex, endIndex).start();
            }
        } else {
            System.err.println("服务器错误!");
        }
    }

    public static class DownLoadThread extends Thread {
        private int threadId;
        private long startIndex;
        private long endIndex;
        private InputStream is;
        private RandomAccessFile raf;

        public DownLoadThread(int threadId, long startIndex, long endIndex) {
            super();
            this.threadId = threadId;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        }
        @Override
        public void run() {
            try {
                long threadLength = endIndex - startIndex;
                long chunkLength = 1024 * 1024 * 5;
                double ceil = Math.ceil(threadLength / chunkLength); //向上取整
                if (threadLength < chunkLength)
                    ceil = 1.0;
                for (long i = 0; i < ceil; i++) {
                    URL url = new URL(path);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setConnectTimeout(5000);
                    conn.setRequestMethod("GET");

                    long threadChunkLength = chunkLength * i + startIndex;
                    if (i != ceil - 1) {
                        // 重要:请求服务器下载部分文件 指定文件的位置
                        conn.setRequestProperty("Range",
                                "bytes=" + threadChunkLength + "-" + (threadChunkLength + chunkLength));
                    } else {
                        conn.setRequestProperty("Range", "bytes=" + threadChunkLength + "-" + endIndex);
                    }
                    is = conn.getInputStream();// 已经设置了请求的位置,返回的是当前位置对应的文件的输入流
                    raf = new RandomAccessFile(filePath, "rwd");
                    raf.seek(threadChunkLength);// 定位文件

                    int len = 0;
                    byte[] buffer = new byte[2048];
                    while ((len = is.read(buffer)) != -1) {
                        raf.write(buffer, 0, len);
                    }
                    System.out.println("线程:" + threadId + "下载完毕");
                }
                System.out.println("共计用时:" + (System.currentTimeMillis() - startTime) + " ms");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                runningThread--;
                if (runningThread == 0) {
                    System.out.println("文件全部下载完毕!");
                }
                try {
                    if (raf != null) raf.close();
                    if (is != null) tis.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}



版权声明:本文为liuxiao723846原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。