RandomAccessFile多线程下载文件

本文详细介绍Java中RandomAccessFile类的基本用法,包括如何读取文件任意位置的数据、追加数据以及在任意位置插入数据,并通过具体示例代码演示这些操作。此外,还介绍了如何利用该类实现多线程断点下载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在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();
                }
            }
        }
    }
}
RandomAccessFile 是 Java 中一种可以随机访问文件的类,可以对文件进行读写操作。多线程读取文件可以提高读取的效率,以下是一个简单的多线程读取文件的示例代码: ```java import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; public class MultiThreadReadFile { private static final int THREAD_COUNT = 4; // 线程数 private static final int BUFFER_SIZE = 1024; // 缓冲区大小 private static final String FILE_PATH = "test.txt"; // 文件路径 public static void main(String[] args) { File file = new File(FILE_PATH); long fileLength = file.length(); // 获取文件长度 long blockSize = fileLength / THREAD_COUNT; // 计算每个线程读取的块大小 for (int i = 0; i < THREAD_COUNT; i++) { long start = i * blockSize; // 计算每个线程读取的起始位置 long end = (i == THREAD_COUNT - 1) ? fileLength : (i + 1) * blockSize; // 计算每个线程读取的结束位置 new Thread(new ReadFileThread(start, end)).start(); // 创建并启动线程 } } static class ReadFileThread implements Runnable { private long start; private long end; public ReadFileThread(long start, long end) { this.start = start; this.end = end; } @Override public void run() { try { RandomAccessFile raf = new RandomAccessFile(FILE_PATH, "r"); raf.seek(start); // 定位到起始位置 byte[] buffer = new byte[BUFFER_SIZE]; int length; while ((length = raf.read(buffer)) != -1 && raf.getFilePointer() <= end) { // 读取文件 System.out.print(new String(buffer, 0, length)); } raf.close(); // 关闭文件 } catch (IOException e) { e.printStackTrace(); } } } } ``` 该示例代码中,多个线程通过计算每个线程需要读取文件块的起始位置和结束位置,分别读取文件。其中,每个线程都使用了 RandomAccessFile 类来读取文件,并且使用了一个缓冲区来提高读取效率。在读取结束后,需要关闭文件流。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路人儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值