Java非阻塞式IO起步

与传统IO的区别

  1. Java NIO( New IO) 是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同, NIO支持面向缓冲区的、基于通道的IO操作,双向的。 NIO将以更加高效的方式进行文件的读写操作。
IONIO
面向流(Stream Oriented)面向缓冲区(Buffer Oriented)
阻塞IO(Blocking IO)非阻塞IO(Non Blocking IO)
(无)选择器(Selectors)
  1. 通道和缓冲区
    Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。

简而言之, hannel 负责传输, Buffer 负责存储

  1. 总体来说NIO有两大点需要掌握
    (1)通道
    (2)缓冲区

缓冲区

  1. 缓冲区(Buffer):在 Java NIO 中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据根据数据类型不同(boolean 除外),提供了相应类型的缓冲区:
    ByteBuffer
    CharBuffer
    ShortBuffer
    IntBuffer
    LongBuffer
    FloatBuffer
    DoubleBuffer
    上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区

  2. 缓冲区存取数据的两个核心方法:
    put() : 存入数据到缓冲区中
    get() : 获取缓冲区中的数据

  3. 缓冲区中的四个核心属性:
    capacity : 容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。
    limit : 界限,表示缓冲区中可以操作数据的大小。(limit 后数据不能进行读写)
    position : 位置,表示缓冲区中正在操作数据的位置。
    mark : 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置

  • 0 <= mark <= position <= limit <= capacity
  1. 直接缓冲区与非直接缓冲区:
    非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
    直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
import java.nio.ByteBuffer;
import org.junit.Test;
public class TestBuffer {
	
	@Test
	public void test3(){
		//分配直接缓冲区
		ByteBuffer buf = ByteBuffer.allocateDirect(1024);
		
		System.out.println(buf.isDirect());
	}
	
	@Test
	public void test2(){
		String str = "abcde";
		
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		buf.put(str.getBytes());
		
		buf.flip();
		
		byte[] dst = new byte[buf.limit()];
		buf.get(dst, 0, 2);
		System.out.println(new String(dst, 0, 2));
		System.out.println(buf.position());
		
		//mark() : 标记
		buf.mark();
		
		buf.get(dst, 2, 2);
		System.out.println(new String(dst, 2, 2));
		System.out.println(buf.position());
		
		//reset() : 恢复到 mark 的位置
		buf.reset();
		System.out.println(buf.position());
		
		//判断缓冲区中是否还有剩余数据
		if(buf.hasRemaining()){
			
			//获取缓冲区中可以操作的数量
			System.out.println(buf.remaining());
		}
	}
	
	@Test
	public void test1(){
		String str = "abcde";
		
		//1. 分配一个指定大小的缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		System.out.println("-----------------allocate()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//2. 利用 put() 存入数据到缓冲区中
		buf.put(str.getBytes());
		
		System.out.println("-----------------put()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//3. 切换读取数据模式
		buf.flip();
		
		System.out.println("-----------------flip()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//4. 利用 get() 读取缓冲区中的数据
		byte[] dst = new byte[buf.limit()];
		buf.get(dst);
		System.out.println(new String(dst, 0, dst.length));
		
		System.out.println("-----------------get()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//5. rewind() : 可重复读
		buf.rewind();
		
		System.out.println("-----------------rewind()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//6. clear() : 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态
		buf.clear();
		
		System.out.println("-----------------clear()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		System.out.println((char)buf.get());	
	}
}

通道Channel

  • 通道( Channel):由 java.nio.channels 包定义的。 Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的“流”。只不过 Channel本身不能直接访问数据, Channel 只能与Buffer 进行交互。
import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Objects;


public class ChannelTest {

    //字符集的编码与解码
    @Test
    public void test5(){
        //获取字符集对象
        Charset charset = Charset.forName("UTF-8");
        //根据字符集对象获取编码器对象和解码器对象
        CharsetEncoder charsetEncoder = charset.newEncoder();
        CharsetDecoder charsetDecoder = charset.newDecoder();

        CharBuffer cb = CharBuffer.allocate(1024);
        String str = "nio字符编解码测试";
        cb.put(str);
        cb.flip();
        try {
            ByteBuffer ebuf = charsetEncoder.encode(cb);
            
            //此时注意:get方法会使position指针后移,
            for (int i = 0; i < ebuf.limit(); i++) {
                System.out.println(ebuf.get());
            }
            
            /**而flip不是转换读取模式,源码中会进行如下操作:
             * limit = position;
             * position = 0;
             * mark = -1;
             * 如果position没有位移,直接使用position会导致limit为0
             */
            ebuf.flip();

            CharBuffer dbuf = charsetDecoder.decode(ebuf);
            System.out.println(dbuf.toString());

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

    //分散于聚集
    @Test
    public void test4(){
        FileChannel inCha = null;
        FileChannel outCha = null;
        try {
            inCha = FileChannel.open(Paths.get("1.jpg"),
                    StandardOpenOption.READ);
            outCha = FileChannel.open(Paths.get("2.jpg"),
                    StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
            ByteBuffer buf1 = ByteBuffer.allocate(100);
            ByteBuffer buf2 = ByteBuffer.allocate(1024);

            ByteBuffer[] bufs = new ByteBuffer[]{buf1, buf2};

            while (inCha.read(bufs) != -1) {
                //多个缓冲区同时转换
                for (ByteBuffer buf : bufs) {
                    buf.flip();
                }

                outCha.write(bufs);
                //写入完成后pos和limit归位
                for (ByteBuffer buf : bufs) {
                    buf.clear();
                }
            }


        } catch (Exception e) {

        } finally {
            try {
                inCha.close();
                outCha.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }



    //通道间传输
    @Test
    public void test3() {
        FileChannel inCha = null;
        FileChannel outCha = null;
        try {
            inCha = FileChannel.open(Paths.get("1.jpg"),
                    StandardOpenOption.READ);
            outCha = FileChannel.open(Paths.get("2.jpg"),
                    StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
            inCha.transferTo(0, inCha.size(), outCha);
        } catch (Exception e) {

        } finally {
            try {
                inCha.close();
                outCha.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void test2() {
        FileChannel inCha = null;
        FileChannel outCha = null;
        try {
            inCha = FileChannel.open(Paths.get("1.jpg"),
                    StandardOpenOption.READ);
            outCha = FileChannel.open(Paths.get("3.jpg"),
                    StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);

            //物理内存映射
            MappedByteBuffer inMap = inCha.map(FileChannel.MapMode.READ_ONLY,
                    0, inCha.size());
            MappedByteBuffer outMap = outCha.map(FileChannel.MapMode.READ_WRITE,
                    0, inCha.size());

            byte[] buf = new byte[inMap.limit()];
            inMap.get(buf);
            outMap.put(buf);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                Objects.requireNonNull(inCha).close();
            } catch (IOException e) {
            }
            try {
                Objects.requireNonNull(outCha).close();
            } catch (IOException e) {
            }
        }

    }

    //利用通道进行文件复制
    //非直接缓冲区方式
    @Test
    public void test1() {
        File file1 = new File("1.jpg");
        File file2 = new File("2.jpg");

        FileOutputStream fos = null;
        FileInputStream fis = null;

        FileChannel chi = null;
        FileChannel cho = null;
        try {
            //创建流对象
            fis = new FileInputStream(file1);
            fos = new FileOutputStream(file2);
            //获取通道
            chi = fis.getChannel();
            cho = fos.getChannel();

            //建立缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            while (chi.read(buffer) != -1) {
                //切换读取模式
                buffer.flip();

                cho.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();

        } finally {
            try {
                fos.close();
                fis.close();
                chi.close();
                cho.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

网络部分

使用 NIO 完成网络通信的三个核心:

  1. 通道(Channel):负责连接

    java.nio.channels.Channel 接口:
     	|--SelectableChannel
     		|--SocketChannel
     		|--ServerSocketChannel
     		|--DatagramChannel
    
     		|--Pipe.SinkChannel
     		|--Pipe.SourceChannel
    
  2. 缓冲区(Buffer):负责数据的存取

  3. 选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况


基于通道的普通阻塞式网络通信演示
import org.junit.Test;

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 NetSocketTest {
    @Test
    public void client(){
        SocketChannel sc = null;
        FileChannel fc = null;
        try {
            sc = SocketChannel.open(new InetSocketAddress("192.168.0.102",18888));
            fc = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
            ByteBuffer buf = ByteBuffer.allocate(1024*1024);
            while (fc.read(buf)!=-1){
                buf.flip();
                sc.write(buf);
                buf.clear();
            }
            sc.shutdownOutput();
        } catch (IOException e) {
        }finally {
            try {
                fc.close();
                sc.close();
            } catch (IOException e) {
            }
        }
    }
    @Test
    public void Server(){
        ServerSocketChannel ssc = null;
        FileChannel fc = null;
        ServerSocketChannel sscPort = null;
        try {
            ssc = ServerSocketChannel.open();
            sscPort = ssc.bind(new InetSocketAddress(18888));
            fc = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
            ByteBuffer buf =ByteBuffer.allocate(1024*1024);
				
			//阻塞式方法
            SocketChannel accept = sscPort.accept();
            while (accept.read(buf) != -1) {
                buf.flip();
                fc.write(buf);
                buf.clear();
            }
            accept.shutdownInput();
        } catch (IOException e) {
        }finally {
            try {
                fc.close();
                sscPort.close();
                ssc.close();
            } catch (IOException e) {
            }
        }
    }
}

基于选择器的无阻塞式网络通信

public class NetSocketTest2 {
    @Test
    public void Client() {
        SocketChannel sc = null;
        FileChannel file = null;
        try {
            sc = SocketChannel.open(new InetSocketAddress("192.168.0.103", 18888));
            file = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
            //切换为非阻塞式
            sc.configureBlocking(false);

            ByteBuffer buf = ByteBuffer.allocate(1024 * 1024);
            while (file.read(buf) != -1) {
                buf.flip();
                sc.write(buf);
                buf.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                file.close();
                sc.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    @Test
    public void Server() {
        ServerSocketChannel ssc = null;
        FileChannel file = null;
        try {
            //将客户端的数据存储到本地
            file = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

            //获取服务端通道
            ssc = ServerSocketChannel.open();
            //设置为非阻塞式
            ssc.configureBlocking(false);
            //绑定服务监听端口号
            ssc.bind(new InetSocketAddress(18888));

            //获取选择器
            Selector selector = Selector.open();

            //选择器监控通道,因此
            //将通道注册选择器上,并设置选择键,选择键是明确选择器监控的是那种状态
            ssc.register(selector, SelectionKey.OP_ACCEPT);

            //迭代选择器上已经准备好了的事件(OP_ACCEPT)
            while (selector.select() > 0) {//如果选择器上准备就绪的事件大于0
                //如果有就绪的事件,就把它们拿出来迭代,
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();

                while (it.hasNext()){
                    SelectionKey selectionKey = it.next();
                    //判断具体什么事件准备就绪,
                    if (selectionKey.isAcceptable()) {//如果是接收事件准备就绪
                        //则获取客户端的套接字通道
                        SocketChannel accept = ssc.accept();
                        //并把套接字通道设置为非阻塞式
                        accept.configureBlocking(false);


                        //得到了客户端的通道后,也注册到选择器上,并监听他的读事件是否就绪
                        //同个下面的代码,就明确了无论是客户端还是服务端获取的通道,
                        // 都可以放在选择器中进行监听状态
                        accept.register(selector, SelectionKey.OP_READ);
                    } else if (selectionKey.isReadable()) {
                        //如果当前选择键位读就绪,就获取他的套接字通道,
                        SocketChannel sc = (SocketChannel) selectionKey.channel();
                        ByteBuffer buf = ByteBuffer.allocate(1024 * 1024);
                        //开始读写数据
                        while (sc.read(buf) > 0) {
                            buf.flip();
                            file.write(buf);
                            buf.clear();
                        }
                    }
                    //如果不是上面if else 语句中的事件,或已经执行完成了的,就可以清空掉。
                    it.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                file.close();
                ssc.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

UDP版无阻塞式

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Scanner;

public class UDPNetSocketTest {
    public static void main(String[] args) {
        new UDPNetSocketTest().Cline();
    }
    @Test
    public void Cline() {
        DatagramChannel dc = null;
        Scanner sc = null;
        try {
            dc = DatagramChannel.open();
            dc.configureBlocking(false);
            ByteBuffer buf = ByteBuffer.allocate(1024);
            sc = new Scanner(System.in);
            String str = null;
            while ((str = sc.next()) != null) {
                buf.put(str.getBytes());
                buf.flip();
                dc.send(buf, new InetSocketAddress("127.0.0.1", 18888));
                buf.clear();
            }
        } catch (IOException e) {
        } finally {
            if (sc != null) {
                sc.close();
            }
            try {
                if (dc != null) {
                    dc.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    @Test
    public void Server() {
        DatagramChannel dc = null;
        try {
            dc = DatagramChannel.open();

            dc.bind(new InetSocketAddress(18888));

            dc.configureBlocking(false);

            Selector selector = Selector.open();
            dc.register(selector, SelectionKey.OP_READ);

            while (selector.select() > 0) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    if (selectionKey.isReadable()) {
                        ByteBuffer buf = ByteBuffer.allocate(1024);

                        dc.receive(buf);
                        buf.flip();
                        System.out.println(new String(buf.array(),0,buf.limit()));
                        buf.clear();
                    }
                    //一定要注意清空
                    iterator.remove();
                }
            }
        } catch (IOException e) {
        } finally {
            try {
                if (dc != null) {
                    dc.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值