多线程文件复制
近期学习NIO技术,了解了有关channel通道、buffer缓存以及selector选择器等技术。萌发了想写一个多点下载的一个简单測试demo。我将这个demo分成两步,第一步先实现将一个文件分段拷贝到一个文件里(通常我们是将文件以流的形式一个字节一个字节的拷贝到目标文件里。如今我们是将文件分段。启用多个线程,每一个线程复制一部分,然后再依据原文件分段的位置组装成一个文件。实现高效的目的)。以下帖源代码
/* * 文件名称:Test.java * 版权:Copyright by AltonSoftware * 描写叙述: * 改动人:gcl * 改动时间:2015年7月6日 * 改动内容: */ package com.file.nio.test; /** * 多线程文件复制,以后扩展网络,能够实现多点下载 * @author gcl * @version 2015年7月6日 * @see Test * @param * @since */ 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) { } } } } }