什么是零拷贝?
我们首先来认识一下传统的I/O操作。
假如说用户进程现在要把一个文件复制到另一个地方。
那么用户程序必须先把这个文件读入内存,然后再把内存里的数据写入另一个文件。
不过文件读入内存也不是直接读入用户进程的内存,而是先读入操作系统内核的内存,然后再从操作系统内核的内存区读到用户进程的内存。
与之对应的是,写文件也不是直接写到磁盘上的文件,而是用户进程先把自己内存的数据传到操作系统内核的内存,然后再从操作系统内核的内存区写到磁盘。而这其中涉及到诸多的系统调用。
因此看上去简单的操作至少要分为四部
1磁盘文件读入操作系统
2操作系统读到用户进程
3用户进程写到操作系统
4操作系统写入磁盘文件

零拷贝和传统I/O有和不同?
零拷贝就是指,传输一个文件的时候,不需要把文件读到用户进程再处理,而是直接把文件读到操作系统一个内存区,然后再移动到操作系统的另一个内存区,最后写入文件。
这样一来,步骤变成这样:
1磁盘文件读入操作系统
2操作系统把数据写入操作系统另一个区域
3操作系统写入磁盘文件
虽然只少了一步,但是这里不仅减少了数据移动的时间损耗,而且减少了系统调用的次数,因此大大缩短了时间。
更加详细的解释请看…

java里如何实现零拷贝呢?
这就要说起java nio中的FileChannel.transferTo()方法了,该方法是把FileChannel中的数据利用零靠的技术转移到另一个channel。这另一个channel往往是FileChannel,不过SocketChannel也是可以的:)。
简单实现(静态下载文件,不能根据用户指令来更改下载的文件。)
代码如下:
单线程版本:

package qiuqi.filedownloadtest;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.Iterator;

public class FileServer {

    
    public static void main(String[] args) throws IOException {


        startServer();
    }

    public static void startServer() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(9999));
        serverSocketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (selector.select() > 0)
        {
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext())
            {
                SelectionKey key = iterator.next();
                iterator.remove();
                if(key.isAcceptable())
                {
                 SocketChannel socketChannel = serverSocketChannel.accept();
                 try (FileInputStream in = new FileInputStream("C:\\Users\\dell\\Desktop\\ZOL手机数据(1).rar")){

                         long size = in.available();
                         long num = 0;
                         long begin = 0;
                         while ( (num = in.getChannel().transferTo(begin,size,socketChannel))!=0)
                         {
                             size-=num;
                             begin += num;
                         }
                     socketChannel.close();

                 }
                 catch (IOException e){e.printStackTrace();}

                }
            }
        }


    }
}

多线程版本:

package qiuqi.filedownloadtest;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FileServer {

    static ExecutorService threadpool = Executors.newCachedThreadPool();
    public static void main(String[] args) throws IOException {

        startServer();
    }

    public static void startServer() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(9999));
        serverSocketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (selector.select() > 0)
        {
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext())
            {
                SelectionKey key = iterator.next();
                iterator.remove();
                if(key.isAcceptable())
                {
                 SocketChannel socketChannel = serverSocketChannel.accept();
                    threadpool.execute(new Runnable() {
                        @Override
                        public void run() {
                            try (FileInputStream in = new FileInputStream("C:\\Users\\dell\\Desktop\\ZOL手机数据(1).rar")){

                         long size = in.available();
                         long num = 0;
                         long begin = 0;
                         while ( (num = in.getChannel().transferTo(begin,size,socketChannel))!=0)
                         {
                             size-=num;
                             begin += num;
                         }
                                socketChannel.close();
                            }
                            catch (IOException e){e.printStackTrace();}
                        }
                    });
                 

                }
            }
        }


    }
}

代码就不讲解了。如果学过java nio,那么理解上面的程序轻而易举。
如果不熟悉java nio的服务器编程那么请先学习再来观看。

最后我想说,java NIO真的是NEW IO即新的IO,而不是NonBlocking IO即非阻塞IO。因为在这套体系里,不仅仅提供了非阻塞的编程模型,而且提供了类似零拷贝,内存映射这样的新技术(对于操作系统来说早就有了)。

java 从网上下载文件的几种方式

package com.github.pandafang.tool;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

import org.apache.commons.io.FileUtils;

/**
 * 文件工具类
 * @author panda fang
 * @date 2017-08-26
 * @version 1.0
 */
public class FileTool {
    
    /**
     * 使用传统io stream 下载文件
     * @param url
     * @param saveDir
     * @param fileName
     */
    public static void download(String url, String saveDir, String fileName) {
        
        BufferedOutputStream bos = null;
        InputStream is = null;
        try {
            byte[] buff = new byte[8192];
            is = new URL(url).openStream();
            File file = new File(saveDir, fileName);
            file.getParentFile().mkdirs();
            bos = new BufferedOutputStream(new FileOutputStream(file));
            int count = 0;
            while ( (count = is.read(buff)) != -1) {
                bos.write(buff, 0, count);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
                
        }
        
    }

    /**
     * 利用 commonio 库下载文件,依赖Apache Common IO ,官网 https://commons.apache.org/proper/commons-io/
     * @param url
     * @param saveDir
     * @param fileName
     */
    public static void downloadByApacheCommonIO(String url, String saveDir, String fileName) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(saveDir, fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 使用NIO下载文件, 需要 jdk 1.4+
     * @param url
     * @param saveDir
     * @param fileName
     */
    public static void downloadByNIO(String url, String saveDir, String fileName) {
        ReadableByteChannel  rbc = null;
        FileOutputStream fos = null;
        FileChannel foutc = null;
        try {
            rbc = Channels.newChannel(new URL(url).openStream());
            File file = new File(saveDir, fileName);
            file.getParentFile().mkdirs();
            fos = new FileOutputStream(file);
            foutc = fos.getChannel();
            foutc.transferFrom(rbc, 0, Long.MAX_VALUE);
            
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (rbc != null) {
                try {
                    rbc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (foutc != null) {
                try {
                    foutc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
    }
    
    /**
     * 使用NIO下载文件, 需要 jdk 1.7+
     * @param url
     * @param saveDir
     * @param fileName
     */
    public static void downloadByNIO2(String url, String saveDir, String fileName) {
        try (InputStream ins = new URL(url).openStream()) {
            Path target = Paths.get(saveDir, fileName);
            Files.createDirectories(target.getParent());
            Files.copy(ins, target, StandardCopyOption.REPLACE_EXISTING);
            
        } catch (IOException e) {
            e.printStackTrace();
        } 
        
    }
}