Java NIO(网络io)(二)

目录

一. 内存映射文件

1. MappedByteBuffer

2.MappedByteBuffer原理

3.FileChannel提供了map方法把文件映射为内存映像文件

4.MappedByteBuffer是ByteBuffer的子类,扩充了三个方法:

5.MappedByteBuffer 拷贝文件

6.拷贝比较 

二.直接缓冲区与非直接缓冲区 

1.ByteBuffer和CharBuffer之间的转换: 

2.创建方式:

3.两者区别 

 三.分散读取与聚集写入

 Scatter 分散

 Gather 聚集

四.JDK1.7的NIO改进

1. 拷贝文件以及将数组内容拷贝到文本中

2.通过URI进行文件的拷贝 

五.NIO的阻塞与非阻塞模式

1.NIO的阻塞模式

5.1.1 客户端发送图片到服务端

2. NIO的非阻塞模式

5.2.1 Selector的创建

5.2.2 向Selector注册通道

5.2.3 Selector监听Channel的四种事件

5.2.4 SelectionKey

5.2.5 NIO非阻塞客户端向服务端发送信息


一. 内存映射文件

1. MappedByteBuffer

java处理大文件,一般用BufferedReader,BufferedInputStream这类带缓冲的IO类,不过,如果文件超大的话,更快的方式是MappedByteBuffer。

MappedByteBuffer是NIO引入的文件内存映射方案,读写性能极高。

MappedByteBuffer将文件直接映射到虚拟内存,如果文件比较大的话,可以分段映射。

2.MappedByteBuffer原理

3.FileChannel提供了map方法把文件映射为内存映像文件

MappedByteBuffer map(int mode,long position,long size);

 mode指出了可访问该内存映像文件的方式:

READ_ONLY,(只读)

READ_WRITE,(读/写)

PRIVATE(私用)

4.MappedByteBuffer是ByteBuffer的子类,扩充了三个方法:

1.force()  :缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件;

2.load()  :将缓冲区的内容载入内存,并返回该缓冲区的引用;

3.isLoaded()  :如果缓冲区的内容在物理内存中,则返回真,否则返回假;

5.MappedByteBuffer 拷贝文件

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class MappedByteBufferTest {
    // MappedByteBuffer 拷贝文件
    public static void MappedByteBufferCopy(String path1 , String path2) throws IOException {
        long startTime = System.currentTimeMillis();
        RandomAccessFile inFile = new RandomAccessFile(path1,"r");
        RandomAccessFile outFile = new RandomAccessFile(path2,"rw");
        FileChannel inFileChannel = inFile.getChannel();
        FileChannel outFileChannel = outFile.getChannel();
        //获取文件大小
        long size = inFile.length();
        /*MappedByteBuffer mbbi = inFileChannel.map(FileChannel.MapMode.READ_ONLY,0,size);
        MappedByteBuffer mbbo = outFileChannel.map(FileChannel.MapMode.READ_WRITE,0,size);
        for (int i = 0 ;i<size;i++) {
            byte b = mbbi.get();
            mbbo.put(b);
        }*/
        //将文件分成几块进行映射
        int blockCount = (int)size/(1024*1024*2);
        //每一块的大小
        int block = (int) size/blockCount;
        //分段映射
        for (int i =0;i<blockCount;i++){
            //MappedByteBuffer进行内存映射
            MappedByteBuffer mbbi = inFileChannel.map(FileChannel.MapMode.READ_ONLY,i*block,block);
            MappedByteBuffer mbbo = outFileChannel.map(FileChannel.MapMode.READ_WRITE,i*block,block);
            //进行拷贝
            byte [] dst = new byte[block];
            mbbi.get(dst);
            mbbo.put(dst);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("MappedByteBuffer copy:" + (endTime-startTime) + "ms");
    }
    public static void main(String[] args) throws IOException {
        String path1 = "D:/Redis-x64-5.0.10.zip";
        String path2 = "D:/Redis-x64-5.0.10111.zip";
        MappedByteBufferCopy(path1,path2);
    }
}

6.拷贝比较 

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class copycompare {
    //普通io拷贝
    public static void ioCopy(String path1,String path2) throws IOException {
        long startTime = System.currentTimeMillis();
        RandomAccessFile inputStream = new RandomAccessFile(path1,"r");
        RandomAccessFile outputStream = new RandomAccessFile(path2,"rw");
        int n = 0;
        byte[] bs = new byte[1024*1024*1];
        while ((n = inputStream.read(bs))!=-1) {
            outputStream.write(bs,0,n);
        }
        //关流
        outputStream.close();
        inputStream.close();
        long endTime = System.currentTimeMillis();
        System.out.println("普通io:" + (endTime-startTime) + "ms");
    }
    //ByteBuffer 拷贝
    public static void bytebufferCopy(String path1,String path2) throws IOException {
        long startTime = System.currentTimeMillis();
        RandomAccessFile inputStream = new RandomAccessFile(path1,"r");
        RandomAccessFile outputStream = new RandomAccessFile(path2,"rw");
        FileChannel inChannel = inputStream.getChannel();
        FileChannel outChannel = outputStream.getChannel();
        ByteBuffer buf = ByteBuffer.allocate(1024*1024*1);
        while (inChannel.read(buf)!=-1){
            buf.flip();
            while (buf.hasRemaining()){
                outChannel.write(buf);
            }
            buf.clear();
        }
        inputStream.close();
        outChannel.close();
        outputStream.close();
        inChannel.close();
        long endTime = System.currentTimeMillis();
        System.out.println("ByteBuffer:" + (endTime-startTime) + "ms");
    }
    public static void main(String[] args) throws IOException {
        String path1 = "D:/Redis-x64-5.0.10.zip";
        String path2 = "D:/Redis-x64-5.0.102222.zip";
        ioCopy(path1,path2);
        bytebufferCopy(path1,path2);
    }
}

二.直接缓冲区与非直接缓冲区 

1.ByteBuffer和CharBuffer之间的转换: 

计算机底层只能存储二进制字节码,因此将字符存储到节点时要对字符进行编码(字符转换成二进制字节码),而从节点取出字符显示时要进行解码(将二进制字节码转换成字符)。

由于Channel只能直接操作ByteBuffer,而处理字符时就需要在CharBuffer和ByteBuffer之间进行转换,刚好Charset提供了这样的功能。 

Charset.defaultCharset();  //这个很关键,也很常用,可以轻松编写平台无关代码。

2.创建方式:

//非直接缓冲区
static ByteBuffer allocate(int capacity)
//直接缓冲区
static ByteBuffer allocateDirect(int capacity)

3.两者区别 

 直接缓冲区:内核地址空间和用户地址空间之间形成了一个物理内存映射文件,减少了之间的copy过程。

 三.分散读取与聚集写入

 Scatter 分散

Scattering Reads是指数据从一个channel读取到多个buffer中

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);

注意: Scattering Reads在移动下一个Buffer前,必须填满当前的Buffer,这也意味着它不适用于动态消息

FileInputStream fis = new FileInputStream(new File("out.txt"));
FileChannel inChannel = fis.getChannel();
ByteBuffer buf1 = ByteBuffer.allocate(8);
ByteBuffer buf2 = ByteBuffer.allocate(8);
ByteBuffer buf3 = ByteBuffer.allocate(4);
ByteBuffer buf4 = ByteBuffer.allocate(20);
ByteBuffer[] bufferArray = {buf1,buf2,buf3,buf4};
inChannel.read(bufferArray);
System.out.println(inChannel.size());
System.out.println((char)buf3.get(3));

 Gather 聚集

Gathering Writes是指数据从多个buffer写入到同一个channel

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = {header,body};
channel.write(bufferArray);

注意: 只有position和limit之间的数据才会被写入。因此与Scattering Reads相反,Gathering Writes能较好地处理动态信息

四.JDK1.7的NIO改进

Path:一个接口,表示文件路径

Paths:有一个静态方法,返回路径(path)

public static Path get(URI uri)
public static Path get(String first,String ... more)

 Files:提供一组静态方法,对文件进行操作        

public static long copy(Path source, OutputStream out)
public static Path write(Path path, Iterable<? extends CharSequence> lines, 
                         OpenOption...options)

1. 拷贝文件以及将数组内容拷贝到文本中

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class Nio1_7 {
    public static void main(String[] args) throws IOException {
        //JDK1.7对NIO的改进
        //1.Path  路径对象
        //2.paths 可以获取Path对象
        //3.Files 提供很多文件的读写操作
        long startTime = System.currentTimeMillis();
        //copy方法
        //Path path = Paths.get("D:/Redis-x64-5.0.10.zip");
        //Files.copy(path,new FileOutputStream("D:/Redis-x64-5.0.10111.zip"));
        //write方法
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        //Path path = Paths.get("D:/","nio.txt");
        Path path = Paths.get("D:/nio.txt");
        //Iterable 可迭代的
        //public static Path write(Path path, Iterable<? extends CharSequence> lines,
        //                         OpenOption...options)
        Files.write(path,list);
        long endTime = System.currentTimeMillis();
        System.out.println("拷贝时间  : " + (endTime-startTime) + "ms");
    }
}

2.通过URI进行文件的拷贝 

//通过URI进行文件的拷贝
URI uri = new URI("file:///D:/nio.txt");
Path path = Paths.get(uri);
Files.copy(path,new FileOutputStream("D:/nio111.txt"));

五.NIO的阻塞与非阻塞模式

1.NIO的阻塞模式

FileChannel只能是阻塞模式,不能切换到非阻塞模式

网络NIO的阻塞模式应用案例:

5.1.1 客户端发送图片到服务端

客户端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.TimeUnit;

public class BlockClient {
    //NIO阻塞模式    TCP协议
    public static void main(String[] args) throws IOException, InterruptedException {
        //1.获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
        //2.分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //3.从本地读取文件,并发送到服务端
        //Path接口,表示文件路径
        //Paths类 get方法返回Path类型的路径
        FileChannel inFileChannel = FileChannel.open(Paths.get("D:/mm.jpg"), StandardOpenOption.READ);
        while (inFileChannel.read(buf)!=-1){
            buf.flip();
            while (buf.hasRemaining()){
                socketChannel.write(buf);
            }
            buf.clear();
        }
        System.out.println("客户端发送数据完成!");
        // 提示服务端,客户端已经发送数据完成,否则服务端一直会阻塞
        //告诉服务端发送数据完成
        socketChannel.shutdownOutput();

        //休眠一段时间
        TimeUnit.MICROSECONDS.sleep(1000);
        //TimeUnit.HOURS.sleep(2);   //休眠2小时
        //TimeUnit.MINUTES.sleep(10);  //休眠10分钟

        // 接收服务端反馈
        int len = 0;
        while((len = socketChannel.read(buf)) !=-1) {
            buf.flip();
            System.out.println(new String(buf.array(),0,len));
        }

        //4. 关闭通道
        inFileChannel.close();
        socketChannel.close();
    }
}

服务端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class BlockServer {

    // NIO的TCP服务端,阻塞模式
    public static void main(String[] args) throws IOException {
        // 1. 获取端口
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2. 绑定链接
        serverSocketChannel.bind(new InetSocketAddress(8888));
        // 3. 获取客户端连接的通道
        System.out.println("服务端等待客户端连接");
        // socketChannel 服务端接收到客户端的socket
        SocketChannel socketChannel = serverSocketChannel.accept();// 阻塞线程
        System.out.println("服务端和客户端已经连接");
        //4. 接收客户端的数据,保存到本地
        FileChannel outFileChannel = FileChannel.open
                (Paths.get("D:/nn.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
        ByteBuffer buf = ByteBuffer.allocate(1024);
        while(socketChannel.read(buf)!= -1){
            buf.flip();
            outFileChannel.write(buf);
            buf.clear();
        }
        System.out.println("服务端将接收的数据存入本地硬盘");

        //发送反馈给客户端
        buf.put("服务端接收数据成功!".getBytes());
        buf.flip();
        socketChannel.write(buf);//服务端数据会原路返回到客户端

        socketChannel.close();
        serverSocketChannel.close();
        outFileChannel.close();
    }
}

2. NIO的非阻塞模式

当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程可以管理多个输入和输出通道。

 NIO通过Selector来管理多个通道:

5.2.1 Selector的创建

Selector selector = Selector.open();

5.2.2 向Selector注册通道

channel.configureBlocking(false);
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);

Channel必须处于非阻塞模式下,才能与Selector一起使用,这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式,而套接字通道都可以。

5.2.3 Selector监听Channel的四种事件

SelectionKey.OP_READ =1 << 0 1读就绪
SelectionKey.OP_WRITE =1 << 2 4写就绪
SelectionKey.OP_CONNECT =1 << 3 8连接就绪
SelectionKey.OP_ACCEPT =1 << 4 16接收就绪
//事件顺序:connect accept read write

如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

5.2.4 SelectionKey

当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些属性:interest集合,ready集合,Channel,Selector,附加的对象(可选)。

5.2.5 NIO非阻塞客户端向服务端发送信息

服务端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class TCPSelectorServer {

    private static final int BUF_SIZE = 1024;
    private static final int PORT = 9999;
    private static final int TIMEOUT = 3000;

    // 网络NIO的非阻塞模式
    // 服务器只利用一个线程,通过选择器来维护多个客户端的请求
    public static void main(String[] args) {
        selector();
    }

    public static void handleAccept(SelectionKey key) throws IOException {
        System.out.println("******handleAccept******");
        ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
        SocketChannel sc = ssChannel.accept();
        // 将客户端的socket设置为非阻塞模式
        sc.configureBlocking(false);
        // 传递到下一步,read
        sc.register(key.selector(),SelectionKey.OP_READ, ByteBuffer.allocateDirect(BUF_SIZE));
    }

    /**
     * @Description 服务端获取客户端信息
     * @param key
     * return void
     */
    public static void handleRead(SelectionKey key) throws IOException {
        System.out.println("******handleRead******");
        SocketChannel sc = (SocketChannel)key.channel();
        ByteBuffer buf = (ByteBuffer) key.attachment();
        long bytesRead = sc.read(buf);
        while(bytesRead>0){
            buf.flip();
            //循环接收客户端发送过来的信息
            while(buf.hasRemaining()){
                System.out.print((char) buf.get());
            }
            System.out.println();
            buf.clear();
            bytesRead = sc.read(buf);
        }
        if (bytesRead == -1){
            sc.close();
        }
    }
    /**
     * @Description 服务端反馈信息到客户端
     * @param key
     * return void
     */
    public static void handleWrite(SelectionKey key) throws IOException {
        System.out.println("******handleWrite******");
        ByteBuffer buf = (ByteBuffer) key.attachment();  //attachment 检测当前附件并返回
        buf.flip();
        SocketChannel sc = (SocketChannel) key.channel();  // 连接客户端的通道
        while (buf.hasRemaining()){
            sc.write(buf);
        }
        buf.compact();
        sc.close();
    }

    public static void selector(){
        Selector selector = null;
        ServerSocketChannel ssc = null;
        try {
            selector = Selector.open();  //打开选择器
            ssc = ServerSocketChannel.open();  // 服务端Socket
            ssc.socket().bind(new InetSocketAddress(PORT));
            //将服务端的 Socket设置成非阻塞模式
            ssc.configureBlocking(false);
            //SelectionKey.OP_READ =1 << 0 1读就绪
            //SelectionKey.OP_WRITE =1 << 2 4写就绪
            //SelectionKey.OP_CONNECT =1 << 3 8连接就绪
            //SelectionKey.OP_ACCEPT =1 << 4 16接收就绪
            //事件顺序:connect ac            register(selector,SelectionKey.OP_ACCEPT);
            while (true) {
                //select() 如果没有参数 服务端一直等待客户端发送请求
                if (selector.select(TIMEOUT) == 0){
                    System.out.println("==");
                    continue;
                }
                //走到这,说明有客户端发送请求
                //SelectionKey存了很多信息
                // interest ready channel selector
                // selector.selectedKeys() 客户端事件集合
                Iterator <SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    if (key.isAcceptable()){// 判断服务端是否为可接收状态
                        handleAccept(key);// 服务端准备接收客户端的请求信息
                    }
                    if (key.isReadable()){// 判断服务端是否为可读状态
                        handleRead(key);// 服务端读取客户端发过来的消息
                    }
                    if (key.isWritable() && key.isValid()){
                        handleWrite(key);
                    }
                    if (key.isConnectable()){
                        System.out.println("isConnectable = true");
                    }
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (selector!= null){
                    selector.close();
                }
                if (ssc != null){
                    ssc.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端:

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;

public class TCPClient {

    // NIO非阻塞模式,TCP客户端
    public static void client(){
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        SocketChannel socketChannel = null;
        try
        {
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false); // 设置为非阻塞模式
            socketChannel.connect(new InetSocketAddress("127.0.0.1",9999));
            TimeUnit.SECONDS.sleep(1);

            if(socketChannel.finishConnect())
            {
                int i=0;
                while(true)
                {
                    TimeUnit.SECONDS.sleep(3);
                    String localIP = InetAddress.getLocalHost().getHostAddress();
                    String info = localIP + " -- I'm " + ++i + "-th information from client";
                    buffer.clear();
                    buffer.put(info.getBytes());
                    buffer.flip();
                    while(buffer.hasRemaining()){
                        System.out.println(buffer);
                        socketChannel.write(buffer); // 发送数据到服务端
                    }
                }
            }
        }
        catch (IOException | InterruptedException e)
        {
            e.printStackTrace();
        }
        finally{
            try{
                if(socketChannel!=null){
                    socketChannel.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws UnknownHostException {
        TCPClient.client();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值