最近学习NIO技术,了解了有关channel通道、buffer缓存以及selector选择器等技术,萌发了想写一个多点下载的一个简单测试demo。我将这个demo分成两步,第一步先实现将一个文件分段复制到一个文件中(通常我们是将文件以流的形式一个字节一个字节的复制到目标文件中,现在我们是将文件分段,启用多个线程,每个线程复制一部分,然后再根据原文件分段的位置组装成一个文件,实现高效的目的)。下面帖源码
/**
* 测试利用多线程进行文件的写操作
* @author gcl
* @version 2015年7月6日
* @see Test
* @param
* @since
*/
package com.file.nio.test;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.Charset;
public class TransferTest {
static Charset charset = Charset.forName("UTF-8");
public static void main(String[] args) throws Exception {
TransferTest.avgFile("D://demo.log");
}
private static void avgFile(String filePath) throws IOException{
File file = new File(filePath);
String fileName = file.getName();
/**
* RandomAccessFile支持任意访问,可以指定到文件的任意位置进行读写操作,该类只支持对文件
* 的操作,不支持其他流
* 初始化该类的第二个参数r标示只读,rw标示支持读写
*/
RandomAccessFile raf = new RandomAccessFile(file, "r");
/**
* 可以看见该文件有多少个字节(byte)
*/
long length = raf.length();
System.out.println("文件长度"+length/1024);
/**
* 通过RandomAccessFile对象获取一个通道,获取这个通道主要是想用它的映射功能
* FileChannel是个抽象类,只能通过InputStream、OutputStream的实现类来getChannel()来
* 获取,
* 比如FileInputStream等等
* 当然还包括RandomAccessFile的getChannel()
*/
FileChannel channel=raf.getChannel();
/**
* 将文件分成等比例的5分,因为我们打算启动5个线程分别复制,因此这里除以5
*/
long avg = length/5;
/**
* 通过进一法取整,要不然除下来,每个线程传13.5byte也不合适,必须是整数字节吧;
* 但是通过进一法取整的话,最后5个想加,很可能大于原文件大小,我们后面会处理,
* 无非就是最后一个线程只传前四个线程传输剩下的部分么,就不用这个进一法取整的值了
*/
int c = (int)Math.ceil(avg);
int i = 0;
/**
* 循环启动5个线程
*/
while(i < 5){
/**
* 这是计算每个线程从文件的那个字节开始复制,我们说了RandomAccessFile
* 支持从指定位置读写操作嘛
*/
int skip = c*i;
/**
* 这里就是我们判断如果最后一个线程读的字节长度大于文件长度时,直接取文件剩下部分
*/
if(skip > length)
skip = (int)length;
/**
* 这就是通过通道channel的map来复制文件的一部分到内存中,得到的是一个
* MappedByteBuffer缓存
* 主意下面几个参数的含义,
* 1:第一个参数标示只读,当然还有MapMode.READ_WRITE标示可读可写,
* 什么数据:。
* 2:第二个参数标示将指针指到文件的那个字节。
* 3:第三个参数标示从skip个位置开始,映射c长度的一段数据到内存,供程序操作
*/
MappedByteBuffer buffer = channel.map(MapMode.READ_ONLY, skip, c);
new FileWriteThread(skip,buffer,fileName).start();
i++;
//清空缓存,因为我们循环要用
buffer.clear();
}
//关闭RandomAccessFile
raf.close();
//关闭FileChannel
channel.close();
}
// 利用线程在文件的指定位置写入指定数据
static class FileWriteThread extends Thread{
//复制文件的开始位置
private int skip;
//字节缓存
private MappedByteBuffer buffer;
//文件名称
private String fileName;
public FileWriteThread(int skip,MappedByteBuffer buffer,String fileName){
this.skip = skip;
this.buffer = buffer;
this.fileName = fileName;
}
public void run(){
RandomAccessFile raf = null;
try {
/**
* RandomAccessFile初始化时,指定的文件路径,如果不存在,则会创建一个,第二个参数
* rw标示支持读写操作
*/
raf = new RandomAccessFile("E://"+fileName, "rw");
/**
* 将指针定位到skip的位置,开始写入数据
*/
raf.seek(skip);
/**
* buffer.remaining()简单的理解就是根据buffer缓存区数据大小创建
* 相应大小的byet数组
*/
byte[] b = new byte[buffer.remaining()];
/**
* 下面这个方法还是贴官网解释吧
* public ByteBuffer get(byte[] dst,int offset,int length)
* 所需的字节(即如果 length > remaining()),则不传输字节且抛出
* BufferUnderflowException。否则,此方法将此缓冲区中的 length 个字节复制到给定
* 将增加 length。
* 换句话说,调用此方法的形式为 src.get(dst, off, len),效果与以下循环语句完全相同:
* for (int i = off; i < off + len; i++)
* dst[i] = src.get();
* 区别在于它首先检查此缓冲区中是否具有足够的字节,这样可能效率更高。
* 参数:
* dst - 向其中写入字节的数组
* offset - 要写入的第一个字节在数组中的偏移量;必须为非负且不大于 dst.length
* length - 要写入到给定数组中的字节的最大数量;必须为非负且不大于 dst.length - offset
*/
buffer.get(b, 0, b.length);
/**
* 开始通过RandomAccessFile写入文件
*/
raf.write(b);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭RandomAccessFile
raf.close();
} catch (Exception e) {
}
}
}
}
}
版权声明:本文为博主原创文章,未经博主允许不得转载。
时间: 2024-08-27 10:53:15