《Netty学习打卡--从小白到放弃》----- 21- netty 之 NIO系统

本文详细介绍Java NIO的基础概念,包括其与传统IO的区别、核心组件通道(Channel)与缓冲区(Buffer)的工作原理,以及如何利用NIO进行高效文件读写操作。同时介绍了选择器(Selector)的概念及其在多路复用中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

打卡日期(2019-07-24)

学习要点

-   1.nio介绍
-   2.nio与传统io的区别
-   3.通道(Channel)和缓冲区(Buffer)
-   4.缓冲区-Buffer
-   5.缓冲区四个核心属性
-   6.直接缓冲区和非直接缓冲区
-   7.通道-Channel
-   8.通道的分类
-   9.通道之间的数据传输
-   10.通道分散(Scatter)与聚集(Gatter)
-   11.选择器-selector
1.nio介绍

  java nio是一个new io,可以替代标准的Java IO API。与原来的IO有同样的作用和目的,但是适用方式完全不同,NIO支持面向缓冲区、基于通道的IO操作。NIO将以更加高效的方式进行文件读写操作。


2.nio与传统io的区别
IONIO
面向流(Stream)面向缓冲区(Buffer)
阻塞IO(Block IO)非阻塞IO(Non Block IO)
选择器(Selectors)

3.通道(Channel)和缓冲区(Buffer)

  java nio的核心就在于通道缓冲区
channel可以看做铁路,负责传输,
buffer可以看做火车,负责数据存取。
若需要使用nio,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。

4.缓冲区-Buffer

缓冲区就是数组,用于存取不同的类型的数据,根据数据类型不同(boolean 除外)提供了相应类型的缓冲区
例如:(
ByteBuffer,
CharBuffer,
IntBuffer,
DouleBuffer,
ShortBuffer,
LongBuffer,
FloatBuffer),上述缓冲区的管理方式都是通过allocate()获取缓冲区

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

0 <= mark <= postion <= limit <= capacity

    //演示capacity,limit,postion之间的关联关系
    @Test
	public void testBuffer(){
		
		String str = "abcdef";
		//初始化容器
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		System.out.println("------------allocate()----------------");
		System.out.println("capacity:"+buf.capacity()); //1024 
		System.out.println("limit:"+buf.limit()); //1024
		System.out.println("position:"+buf.position()); //0
		
		//往buf中put值
		buf.put(str.getBytes());
		
		System.out.println("------------put()----------------");
		System.out.println("capacity:"+buf.capacity()); //1024 
		System.out.println("limit:"+buf.limit()); //1024
		System.out.println("position:"+buf.position()); //6
		
		
		//put完值之后切换读模式flip
		buf.flip();
		//1.此时limit的位置就是当前postion的位置
		//2.postion的位置重置为0
		
		System.out.println("------------flip()----------------");
		System.out.println("capacity:"+buf.capacity()); //1024 
		System.out.println("limit:"+buf.limit()); //6
		System.out.println("position:"+buf.position()); //0
		
		
		System.out.println("------------get()----------------");
		//get()从缓冲区中读取数据
		byte[] dst = new byte[buf.limit()];
		buf.get(dst);
		System.out.println("capacity:"+buf.capacity()); //1024 
		System.out.println("limit:"+buf.limit()); //6
		System.out.println("position:"+buf.position()); //6
		System.out.println(new String(dst,0,dst.length));
		
		
		//rewind之后可以重新进行读取数据,可重复读取数据
		buf.rewind();
		
		System.out.println("------------rewind()----------------");
		System.out.println("capacity:"+buf.capacity()); //1024 
		System.out.println("limit:"+buf.limit()); //6
		System.out.println("position:"+buf.position()); //0
		
		
		//clear清空缓冲区,但是里面的数据并没有被清空,只是capacity,limit,postion恢复到初始化位置。
		//clear之后,缓冲区的数据处于"被遗忘"状态
		buf.clear();
		//clear之后,原先的数据还在,只不过position为0,当你重新插入数据的时候,会把原先的数据给覆盖掉
		
		System.out.println("------------clear()----------------");
		System.out.println("capacity:"+buf.capacity()); //1024 
		System.out.println("limit:"+buf.limit()); //1024
		System.out.println("position:"+buf.position()); //0
		
		System.out.println((char)buf.get());//a
    //mark作用,记录当前postion的位置,当调用reset()方法之后,恢复到记录postion的位置
    @Test
	public void testMark(){
		String str = "abcdef";
		//初始化容器
		ByteBuffer buf = ByteBuffer.allocate(1024);
		//往buf中put值
		buf.put(str.getBytes());
		//put完值之后切换读模式flip
		buf.flip();
		byte[] dst = new byte[buf.limit()];
		//从缓冲区中读取两个字节,从0开始取2个
		buf.get(dst,0,2);
		System.out.println(new String(dst,0,2));//ab
		System.out.println("position:"+buf.position());//2
		
		//对当前的postion进行标记
		buf.mark();//此时的postion位置2
		
		//从缓冲区中再读取两个,从第2位开始读取2个
		buf.get(dst,2,2);
		System.out.println(new String(dst,2,2));//cd
		//此时的postion的位置是4
		System.out.println("position:"+buf.position());//4
		
		//恢复到mark标记的位置
		buf.reset();
		System.out.println("position:"+buf.position());//2
		
	}
6.直接缓冲区和非直接缓冲区
  • 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM内存当中。
  • 直接缓冲区:通过allocateDirect()方法分配缓冲区,将缓冲区建立在物理内存当中,可以适当提高效率。
7.通道-Channel

  Channel表示io源与目标打开的链接,Channel类似于传统的“流”。只不过Channel本身不能直接访问数据,Channel只能与缓冲区(Buffer)进行交互

8.通道的分类
  • FileChannel:从文件中读写数据
  • SocketChannel:通过TCP读写网络中的数据
  • ServerSocketChannel:可以监听新捡来的TCP链接,像Web服务器那样,对每一个新进来的链接都会创建一个SocketChannel
  • DatagramChannel:能从UDP中读写网络中的数据
  1. 针对通道类型提供了getChannel()方法
  2. jdk1.7中的nio.2针对各个通道提供了静态方法open()
  3. jdk1.7中的nio.2的Files工具类的newByteChannel()
//方式1-getChannel()利用通道完成文件复制
@Test
public void testGetChannel() throws Exception{
	FileInputStream fis = new FileInputStream("1.png");
	FileOutputStream fos = new FileOutputStream("2.png");
	
	//获取通道
	FileChannel inChannel = fis.getChannel();
	FileChannel outChannel = fos.getChannel();
	
	//指定缓存区大小
	ByteBuffer buf = ByteBuffer.allocate(1024);
	
	//将通道中的数据写入缓存
	while(inChannel.read(buf) != -1){
		//切换读写模式
		buf.flip();
		//将缓存中的数据写入通道
		outChannel.write(buf);
		//清空缓存区
		buf.clear();
	}
	
	outChannel.close();
	inChannel.close();
	fos.close();
	fis.close();
}
//方式2-open()利用通道完成文件复制
//使用直接缓冲区完成文件复制(内存映射文件)
@Test
public void testOpen() throws Exception{
	//1.获取通道
	FileChannel inChannel = FileChannel.open(Paths.get("1.png"),StandardOpenOption.READ);
	FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
	
	//内存映射文件
	MappedByteBuffer inBuf =  inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
	MappedByteBuffer outBuf = outChannel.map(MapMode.READ_WRITE,0, inChannel.size());
	
	//直接对缓冲区数据进行读写操作
	byte[] dst = new byte[inBuf.limit()];
	inBuf.get(dst);
	outBuf.put(dst);
	
	//关闭通道
	inChannel.close();
	outChannel.close();
}


9.通道之间的数据传输

将数据从一个通道复制到另一个通道

  • transferFrom()
  • transferTo()
//方式3-testtransfer()利用通道完成文件复制
//通道之间数据传输(直接缓冲区)
@Test
public void testNewByteChannel() throws Exception{
	//1.获取通道
	FileChannel inChannel = FileChannel.open(Paths.get("1.png"),StandardOpenOption.READ);
	FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
	//transferTo 通道文件复制,从0开始:通道大小:输出通道
	//transferFrom 通道文件copy,输入通道:0开始:通道大小
	inChannel.transferTo(0, inChannel.size(), outChannel);
	//outChannel.transferFrom(inChannel, 0, inChannel.size());
	inChannel.close();
	outChannel.close();
}

10.分散(Scatter)与聚集(Gatter)
  • 分散读取(Scatter read):将通道的数据分散到多个缓冲区中
  • 聚集写入(Gatter write):将多个缓冲区中的数据聚集写入到通道中
@Test
public void testSGatter() throws Exception{
	RandomAccessFile raf1 = new RandomAccessFile("1.png","rw");
	
	//获取通道
	FileChannel inchannel = raf1.getChannel();
	//指定缓冲区大小
	ByteBuffer buf1 = ByteBuffer.allocate(100);
	ByteBuffer buf2 = ByteBuffer.allocate(1024);
	
	//分散到缓冲区 Scatter
	ByteBuffer[] bufs = {buf1,buf2};
	inchannel.read(bufs);
	
	for(ByteBuffer bb : bufs){
		bb.flip();
		//分别获取缓冲中的数据
		System.out.println(new String(bb.array(),0,bb.limit())) ;
	}
	
	RandomAccessFile raf2 = new RandomAccessFile("2.png", "rw");
	FileChannel outChannel = raf2.getChannel();
	
	//聚集写入通道 Gatter
	outChannel.write(bufs);
	
	inchannel.close();
	outChannel.close();
	
}
11.选择器-selector

  Selector称为选择器,当然也可以翻译为多路复用。是java nio核心组件中的一个,用于检查一个或者多个通道(Channel)的状态是否处于可读,可写状态。

  Selector管理的通道必须是非阻塞的,因为FileChannel为阻塞channel(没有继承SelectableChannel),所以文件通道不能被管理。

//通道设置非阻塞
channel.configureBlocking(false);
//客户端非阻塞发送消息
@Test
public void client() throws IOException{
	
	// 1. 获取通道
	SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
	
	// 2. 设置通道非阻塞
	socketChannel.configureBlocking(false);
	
	// 3. 声明缓存大小
	ByteBuffer buf = ByteBuffer.allocate(1024);
	
	// 4. 缓冲区存放数据
	LocalDateTime date = LocalDateTime.now();
	buf.put(("java nio 获取当前时间"+date.toString()).getBytes());
	
	// 5. 往通道中写入数据
	while(socketChannel.read(buf) != -1){
		// 切换读状态
		buf.flip();
		socketChannel.write(buf);
		buf.clear();
	}
	
	socketChannel.close();
}
//服务端非阻塞接收消息
@Test
public void server() throws IOException {
	// 1.获取通道
	ServerSocketChannel ssChannel = ServerSocketChannel.open();	
	
	// 2.设置非阻塞通道
	ssChannel.configureBlocking(false);
	//通道绑定端口号
	ssChannel.bind(new InetSocketAddress(8888));
	
	
	// 3.创建选择器Selector
	Selector selector = Selector.open();
	
	// 4.将通道注册到选择器当中,并且指定”监听接收事件“
	ssChannel.register(selector, SelectionKey.OP_ACCEPT);
	
	// 5.轮训获取所有已经"准备好"的事件
	while(selector.select() > 0){
		// 6.获取当前选择器中所有注册的"已经就绪的监听事件"
		Iterator<SelectionKey> sks = selector.selectedKeys().iterator();
		
		while(sks.hasNext()){
			
			// 7.获取准备就绪的状态
			SelectionKey sk = sks.next();
			
			// 8.判断是什么事件准备就绪
			if(sk.isAcceptable()){
				
				// 9.若接收就绪,就获取客户端的链接
				SocketChannel sc = ssChannel.accept();
				
				// 10.客户端通道设置非阻塞
				sc.configureBlocking(false);
				
				// 11.将改通道注册到选择器上
				sc.register(selector, SelectionKey.OP_READ);
			}else if(sk.isReadable()){
				
				//System.out.println("开始读取数据...");
				// 12.获取当期选择器的读就绪状态的通道
				SocketChannel sc = (SocketChannel) sk.channel();
				
				// 13.读取数据
				ByteBuffer buf = ByteBuffer.allocate(1024);
				
				int len = 0;
				while((len = sc.read(buf)) > 0){
					buf.flip();
					System.out.println(new String(buf.array(),0,len));
					buf.clear();
				}
			}
			// 14.取消选择键
			sks.remove();
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值