今天结合文件传输,记录一下内存映射方式读写文件,的确很快。网上都是用例子说了一下
MappedByteBuffer类的使用,其实还是那些用法,只是想把封装给大家说说。
基本用法一般给大家的代码如下:
FileChannel fc = new RandomAccessFile("D:/TEST/test3.txt", "rw").getChannel();
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, length);
for (int i = 0; i < length; i++) {
mbb.put((byte) 'a');
}
FileChannel fileChannel = null;
try {
String filePath = "D:\\temp\\avatar.jpg";
fileChannel = new RandomAccessFile(new File(filePath), "rw").getChannel();
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size());
byte[] bytes = new byte[(int) fileChannel.size()];
mappedByteBuffer.get(bytes);
print(bytes);
} catch (Exception e) {
e.printStackTrace();
}
这里先说写代码,如果我们说大文件,比如说20G的写入,则需要分段写。一次写入的数据量一定要比映射的区间小。
/**
* 写入
* @param src
* @throws IOException
*/
public void writeFileBuffer(ByteBuffer src) throws IOException {
if (randomAccessFile == null) {
long curLen = fileLen - writeLen;//计算剩余的字节
if (curLen > size) {
curLen = size;
}
try {
randomAccessFile = new RandomAccessFile(writeFile, "rw");
// randomAccessFile.setLength(fileLen);
FileChannel rafchannel = randomAccessFile.getChannel();
//mmap 使得jvm堆和pageCache有一块映射空间
MappedByteBuffer map = rafchannel.map(FileChannel.MapMode.READ_WRITE, writeLen, curLen); // 1000M的pageCache大小
map.put(src);
writeMap = map;
writeLen += src.limit();
foceLen=src.limit();
} catch (Exception e) {
e.printStackTrace();
}
} else {
if (foceLen + src.limit() > size) {
//重新加载
long curLen = fileLen - writeLen;
if (curLen > size) {
curLen = size;
}
MappedByteBuffer map = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, writeLen, curLen); // 1000M的pageCache大小
map.put(src);
writeMap = map;
writeLen += src.limit();
foceLen = src.limit();
} else {
writeMap.put(src);
foceLen += src.limit();
writeLen += src.limit();
}
}
}
如果size大小的映射空间不够类,就需要从已经写入的大小(当前文件末尾),从新映射区间,然后继续写入。文件快写完时,也不能太多,多余的就是空的,二进制文件就麻烦了。要计算刚刚好。在这里传输的时候,先通知对方传输的文件名称和大小,这样就好了。
foceLen用了记录映射区间已经使用的大小,新写入不够就重新映射。这样连续写完为止。
读取20G大文件原来一样。
/**
* 读取文件
* @param path
* @throws IOException
*/
public void readFile(String path) throws IOException {
randomAccessFile = new RandomAccessFile(path, "rw");
}
/**
* 读取数据
* @return
* @throws IOException
*/
public ByteBuffer read() throws IOException {
if (randomAccessFile != null) {
long pos = readNum * size;//已经读取的字节
long leftNum = randomAccessFile.getChannel().size() - pos;//剩下字节
readNum++;
try {
if (leftNum > size) {
MappedByteBuffer map = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, pos, size); // 1000M的pageCache大小
return map.asReadOnlyBuffer();
} else {
isRead=true;
MappedByteBuffer map = randomAccessFile.getChannel().map(FileChannel.MapMode.PRIVATE, pos, leftNum); // 1000M的pageCache大小
randomAccessFile.close();//最后一次了
return map.asReadOnlyBuffer();
}
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
return null;
}
每次读取size大小,我这里说1000M。最后一次读取就标记读取完了。
完整类
package MappedByteBufferFile;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MMapBufferChanel {
/**
* 读写缓存
*/
private final int size = 4096 * 1024 * 250;
public int getSize() {
return size;
}
private RandomAccessFile randomAccessFile = null;
/**
* 读取次数
*/
private long readNum = 0;
private boolean isRead=false;
/**
* 写入的文件
*/
private String writeFile;
/**
* 写入的
*/
private MappedByteBuffer writeMap = null;
/**
* 已经写的缓存
*/
private long foceLen=0;
/**
* 写的文件长度
*/
private long fileLen;
/**
* 已经写入的长度
*/
private long writeLen = 0;
/**
* 读取完成
* @return
*/
public boolean getRead()
{
return isRead;
}
public void setFileLen(long len) {
this.fileLen = len;
}
public void setWriteFile(String file) {
this.writeFile = file;
}
public void close() throws IOException {
if(randomAccessFile!=null)
{
randomAccessFile.close();
}
}
/**
* 写入
* @param src
* @throws IOException
*/
public void writeFileBuffer(ByteBuffer src) throws IOException {
if (randomAccessFile == null) {
long curLen = fileLen - writeLen;//计算剩余的字节
if (curLen > size) {
curLen = size;
}
try {
randomAccessFile = new RandomAccessFile(writeFile, "rw");
// randomAccessFile.setLength(fileLen);
FileChannel rafchannel = randomAccessFile.getChannel();
//mmap 使得jvm堆和pageCache有一块映射空间
MappedByteBuffer map = rafchannel.map(FileChannel.MapMode.READ_WRITE, writeLen, curLen); // 1000M的pageCache大小
map.put(src);
writeMap = map;
writeLen += src.limit();
foceLen=src.limit();
} catch (Exception e) {
e.printStackTrace();
}
} else {
if (foceLen + src.limit() > size) {
//重新加载
long curLen = fileLen - writeLen;
if (curLen > size) {
curLen = size;
}
MappedByteBuffer map = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, writeLen, curLen); // 1000M的pageCache大小
map.put(src);
writeMap = map;
writeLen += src.limit();
foceLen = src.limit();
} else {
writeMap.put(src);
foceLen += src.limit();
writeLen += src.limit();
}
}
}
/**
* 读取文件
* @param path
* @throws IOException
*/
public void readFile(String path) throws IOException {
randomAccessFile = new RandomAccessFile(path, "rw");
}
/**
* 读取数据
* @return
* @throws IOException
*/
public ByteBuffer read() throws IOException {
if (randomAccessFile != null) {
long pos = readNum * size;//已经读取的字节
long leftNum = randomAccessFile.getChannel().size() - pos;//剩下字节
readNum++;
try {
if (leftNum > size) {
MappedByteBuffer map = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, pos, size); // 1000M的pageCache大小
return map.asReadOnlyBuffer();
} else {
isRead=true;
MappedByteBuffer map = randomAccessFile.getChannel().map(FileChannel.MapMode.PRIVATE, pos, leftNum); // 1000M的pageCache大小
randomAccessFile.close();//最后一次了
return map.asReadOnlyBuffer();
}
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
return null;
}
}
全部是ByteBuffer是因为我使用的是sockchannel。这里虽然读写一个类,但是使用必须是不同实例。
如果只是读取20G一瞬间。写入速度不稳,笔记本机械硬盘70M-100M.我使用的还有传输阻塞,传完就读完。传输阻塞慢,所以读取就和传输一样。如果内存足够异步读取,那就说读完很久才传输完。sock缓存设置10M,传输130M/s.
完整的代码会上传csdn.
git地址:
https://github.com/jinyuttt/MappedByteBufferFile.git