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

时间:2022-02-25 20:14:23

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

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

具体注意事项我会全部写在注释中,我就不再啰嗦介绍RandomAccessFile怎么去用,它跟普通文件读写类似,不过就是多了一个游标,可以从文件指定位置开始读取,大家可以一边参照API,一边看我的Demo执行情况,试着自己写写。
大家有好的改进意见、建议或者有什么问题欢迎留言。

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();
}
}
}
}