在java集合中我们了解到ArrayList 实现的一个接口 RandomAccess,而该接口只是一个标记接口,仅表示支持随机访问。
同理,根据名字可知RandomAccessFile类支持随机读写文件。RandomAccessFile类中比较重要的2个方法,其他的和普通IO类似,在这里,就不详细说明了。
- getFilePointer() 返回文件记录指针的当前位置
- seek(long pos) 将文件记录指针定位到pos的位置
1、常见使用方式:
1)读取任意位置的数据:
- 创建randomAccessFile对象,以读的方式打开文件。RandomAccessFile raf=new RandomAccessFile(path, “r”);
- 调用seek(long pos) 方法seek到文件的指定位置(按照字节去seek)。
- 调用randomAccessFile对象的read方法读取数据;
2)追加数据:
- 创建randomAccessFile对象,以写的方式打开文件。RandomAccessFile raf=new RandomAccessFile(path, “w”);
- 调用seek(long pos) 方法seek到文件的尾部(raf.seek(raf.length()); )。
- 调用raf的write方法写数据。
3)任意位置插入数据:
如果文件中已经有内容,则:
- 创建randomAccessFile对象,以读写的方式打开文件。RandomAccessFile raf=new RandomAccessFile(path, “rw”);
- 创建文件缓冲区;
- 调用seek(long pos) 方法seek到文件的指定位置,然后把剩下内容写入到缓冲区中;
- 调用seek(long pos)方法返回到指定位置,把新内容写入;
- 吧缓冲区内容追加到文件尾部。
如果是一个新文件,可以先调用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 版权协议,转载请附上原文出处链接和本声明。