Java NIO

Buffer

一个容器,用来存储需要传递的数据。 常见分类如下:
在这里插入图片描述

Buffer创建

Buffer分为两种,直接缓冲区与非直接缓冲区:
非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
在这里插入图片描述
直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
在这里插入图片描述

Buffer的使用

三个常用的属性:

属性说明
容量(Capacity)缓冲区能够容纳的数据元素的最大数量,缓冲区创建时被设定,永远不能被改变
上界(Limit)缓冲区第一个不能被读或写的元素,或者说缓冲区中现存元素的计数
位置(Position)下一个要被读或写的元素的索引,位置会自动由相应的get()和put()方法更新

用法实验

put方法:将数据存储到容器里。

public static void main(String[] args) {
	// 创建一个ByteBuffer,容量为10
	ByteBuffer byteBuffer = ByteBuffer.allocate(10);
	// 看一下初始时4个核心变量的值
	System.out.println("初始时-->limit--->" + byteBuffer.limit());
	System.out.println("初始时-->position--->" + byteBuffer.position());
	System.out.println("初始时-->capacity--->" + byteBuffer.capacity());
	System.out.println("--------------------------------------");

	// 添加一些数据到缓冲区中
	String s = "bobo";
	byteBuffer.put(s.getBytes());

	// 看一下初始时4个核心变量的值
	System.out.println("put完之后-->limit--->" + byteBuffer.limit());
	System.out.println("put完之后-->position--->" + byteBuffer.position());
	System.out.println("put完之后-->capacity--->" + byteBuffer.capacity());
}
初始时-->limit--->10
初始时-->position--->0
初始时-->capacity--->10
--------------------------------------
put完之后-->limit--->10
put完之后-->position--->4
put完之后-->capacity--->10

flip方法:可以将Buffer切换为只读模式,那么我们可以读position 0 到limit长度的数据。如下:

byteBuffer.flip();
System.out.println("flip完之后-->limit--->" + byteBuffer.limit());
System.out.println("flip完之后-->position--->" + byteBuffer.position());
System.out.println("flip完之后-->capacity--->" + byteBuffer.capacity());
flip完之后-->limit--->4
flip完之后-->position--->0
flip完之后-->capacity--->10

get方法:借助position读取数据。

// 一个字节一个字节的读取
System.out.println((char)byteBuffer.get());
System.out.println((char)byteBuffer.get());
System.out.println((char)byteBuffer.get());
System.out.println((char)byteBuffer.get());
System.out.println("get完之后-->mark--->" + byteBuffer.mark());
// get方法 读取了多少个字节,position就会移动多少位,相应再次重新读取需要flip
byteBuffer.flip();
byte[] b = new byte[byteBuffer.limit()];
// 批量读取数据
byteBuffer.get(b);
System.out.println(new String(b,0,b.length));
b
o
b
o

bobo

rewind方法:当调用get()读完一遍数据后,我们还想再读取一遍,此时可以考虑rewind方法恢复到上一次状态。

clear方法:数据操作完成后我们还想要继续写入数据,这时我们可以使用clear方法来’清空’缓冲区。数据没有真正被清空,只是被遗忘掉了

mark方法:标记position的位置,在往后读取的过程中,如果想从该标记的位置读取,则需要再调用reset()即可。

hasRemaining方法:检查position和limit之间是否还有元素。判断是否还有剩余元素,与remaining读取剩余个数相互配合。

总结
在这里插入图片描述

Channel

一个通道,用于将容器中的数据从一个地方放到另一地方。

Channel的发展史

在这里插入图片描述
关于DMA的介绍:https://baike.baidu.com/item/DMA/2385376?fr=aladdin
在这里插入图片描述
在这里插入图片描述
可以单纯的理解,channel的出现是为了减轻CPU的负载。可以把channel理解为处理器,与GPU的出现意图差不多。

Channel的分类

java.nio.channels.Channel 接口
    |-- FileChanel
    |-- SelectableChannel
        |-- SocketChanel
        |-- ServerSockerChanel
        |-- DatagramChanel

Channel的用法

channel如何获取

1:Java 针对支持通道的类提供了 getChannel() 方法
    本地IO:
        FileInputStream / FileOutputStream
        RandomAccessFile
    网络IO:
        Socket
        ServerSocket
        DatagramSocket
        
2:在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open()
3:在 JDK 1.7 中的 NIO.2 的 Files 工具类的 newByteChannel()

channel如何操作buffer
可以想象buffer是车,channel是铁路,那么将数据装满buffer后,就可以放到channel上。

// 不等于-1就说明读取到数据,读取到数据后buff要切换到读取模式才能获取数据
sChannel.read(buff)
buff.flip();
buff.get();
// 写数据可以通过下面这种
sChannel.write(buff);

方法2:使用直接缓冲区完成文件的复制,直接缓冲区

@Test
public void testUseChannelToCopyFile2() throws Exception {
    FileChannel inChannel = FileChannel.open(
            Paths.get("/Users/gxc/tmp/sl.mp4"),
            StandardOpenOption.READ);

    FileChannel outChannel = FileChannel.open(
            Paths.get("/Users/gxc/tmp/sl3.mp4"),
            StandardOpenOption.READ,
            StandardOpenOption.WRITE,
            StandardOpenOption.CREATE_NEW);

    // 内存映射文件
    MappedByteBuffer inMappedBuf = inChannel.map(
            FileChannel.MapMode.READ_ONLY, 0,
            inChannel.size());

    MappedByteBuffer outMappedBuf = outChannel.map(
            FileChannel.MapMode.READ_WRITE, 0,
            inChannel.size());

    // 直接对缓冲区 进行数据的读写操作
    byte[] dst = new byte[inMappedBuf.limit()];
    inMappedBuf.get(dst);
    outMappedBuf.put(dst);

    inChannel.close();
    outChannel.close();
}

Selector

Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。

传统的Socket编程需要服务端不断的阻塞等待,极其浪费资源,NIO是对通讯服务区的一种改善,示意图如下:
在这里插入图片描述
传统的阻塞代码:

@Test
	public void client() throws Exception{
		//创建通道
		FileChannel open = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.READ);
		//分配缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		//创建socket通道
		SocketChannel open2 = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9988));
		//读取本地文件,并发送到服务端
		while(open.read(buffer)!=-1){
			buffer.flip();
			open2.write(buffer);
			buffer.clear();
		}
		//强制告诉服务端,已发送完数据,否则无法读取服务端的返回数据
		open2.shutdownOutput();
		//获取服务端反馈
		int len=0;
		while((len=open2.read(buffer))!=-1){
			buffer.flip();
			System.out.println(new String (buffer.array(),0,len));
			buffer.clear();
		}
		open.close();
		open2.close();
	};
	
	@Test
	public void server() throws IOException{
		// 获取通道
		FileChannel open = FileChannel.open(Paths.get("11.jpg"), StandardOpenOption.WRITE,
				StandardOpenOption.CREATE);
		ServerSocketChannel open2 = ServerSocketChannel.open();
		//分配指定大小的缓冲区
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		//绑定连接
		open2.bind(new InetSocketAddress(9988));
		//获取客户端连接的通道
		SocketChannel accept = open2.accept();
		//接收客户端的数据,并保存到本地
		while(accept.read(byteBuffer)!=-1){
			byteBuffer.flip();
			open.write(byteBuffer);
			byteBuffer.clear();
		}
		
		//发送反馈给客户端
		byteBuffer.put("图片收到了".getBytes());
		byteBuffer.flip();
		accept.write(byteBuffer);
		accept.close();
		open.close();
		open2.close();
	}

非阻塞式 socket 通信

	@Test
	public void client() throws Exception{
		//1. 获取通道
		SocketChannel clientSoc=SocketChannel.open(new InetSocketAddress("127.0.0.1", 9988));
		//2. 切换非阻塞模式
		clientSoc.configureBlocking(false);
		//3. 分配指定大小的缓冲区
		ByteBuffer allocate = ByteBuffer.allocate(1024);
		//4. 获取用户输入数据,发送数据给服务端
		Scanner sc=new Scanner(System.in);
		while(sc.hasNext()){
			String next = sc.next();
			allocate.put((LocalDate.now()+"--客户端说"+"\n"+next).getBytes());
			allocate.flip();
			clientSoc.write(allocate);
			allocate.clear();
		}
		sc.close();
		//5. 关闭通道
		clientSoc.close();
	}
	
	
	@Test
	public void Server() throws Exception{
		//1. 获取通道,切换非阻塞模式
		ServerSocketChannel serSoc=ServerSocketChannel.open();
		serSoc.configureBlocking(false);
		serSoc.bind(new InetSocketAddress(9988));
		//2. 获取选择器
		Selector selector = Selector.open();
		//3. 将通道注册到选择器上, 并且指定“监听接收事件”
		serSoc.register(selector,  SelectionKey.OP_ACCEPT);
		//4. 轮询式的获取选择器上已经“准备就绪”的事件
		while(selector.select()>0){
			//5. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
			Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
			while(iterator.hasNext()){
				SelectionKey sk = iterator.next();
				//6. 判断具体是什么事件准备就绪
				if(sk.isAcceptable()){
					//7. 若“接收就绪”,获取客户端连接,切换非阻塞模式,并将该通道注册到选择器上
					SocketChannel sChannel = serSoc.accept();
					sChannel.configureBlocking(false);
					sChannel.register(selector, SelectionKey.OP_READ);
				}else if(sk.isReadable()){
					//8. 获取当前选择器上“读就绪”状态的通道
					SocketChannel sChannel = (SocketChannel) sk.channel();
					//9. 读取数据
					ByteBuffer buf = ByteBuffer.allocate(1024);
					int len = 0;
					while((len = sChannel.read(buf)) > 0 ){
						buf.flip();
						System.out.println(new String(buf.array(), 0, len));
						buf.clear();
					}
				}
			}
			//10. 每次处理完一个选择键事件,都要取消选择键 SelectionKey
			iterator.remove();
		}
		
	}

非阻塞式的操作思路:

  1. 通过调用 sChannel.configureBlocking(false); 改阻塞为非阻塞,后面每个channel 都需要。
  2. 所要使用cannel注册到selector,由selector来调度:sChannel.register(selector, int ops);
  3. 对于ops,可以理解为一个个的事件,常见如下,如果要注册的不止一个事件,可以使用 | 操作符连接:
    读 : SelectionKey.OP_READ (1)
    写 : SelectionKey.OP_WRITE (4)
    连接 : SelectionKey.OP_CONNECT (8)
    接收 : SelectionKey.OP_ACCEPT (16)
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值