一篇通俗易懂的文章初探NIO
Java NIO
文章目录
- Java NIO
- 简介
- 什么是Java NIO
- NIO与IO的区别
- 通道与缓冲区
- 缓冲区(Buffer)
- 缓冲区的结构
- 缓冲区的字段(属性)
- 缓冲区的方法
- 深入了解缓冲区的属性与方法
- 通道(Channel)
- 通道的结构
- 通道的方法
- 深入了解非直接与直接缓冲区的区别
- 非直接与直接缓冲区+通道运输
- transferFrom,transferTo 通道运输
- 分散(Scatter)与聚集(Gather)
- 字符集
- 阻塞与非阻塞
- 阻塞
- 非阻塞
- 选择器(Selector)
- SocketChannel
- DetagramChannel
- Pipe
简介
什么是Java NIO
NIO :non-blocking IO (非阻塞IO)
Java NIO 是 Java1.4 引入的一个新的IO API
NIO与IO有同样的作用和目的,都是为了读写文件
NIO与IO的区别
NIO | IO |
---|---|
非阻塞IO | 阻塞IO |
面向缓冲区(双向) | 面向流(单向) |
选择器 |
总结:NIO支持面向缓冲区(Buffer),通道(channel)的IO操作,使用选择器来实现非阻塞IO,以更高效的方式进行文件的读写
下文将对NIO的三个重要的新名词:缓冲区(Buffer),通道(Channel),选择器(selector) 进行描述
通道与缓冲区
通道是打开IO设备的连接,缓冲区是用于容纳数据的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
通道用于传输,缓冲区用于存取
每个通道可以连接一个IO设备,所以读写文件一般使用两个通道
可以想象缓冲区运行在通道上,所以一般本地读写文件使用一个缓冲区即可
缓冲区(Buffer)
缓冲区的结构
java.nio包中有Buffer抽象类,它有着除boolean类型外的代表七个基本类型的子类
它们基本类似,只是各自管理的数据类型不同(后面使用ByteBuffer来举例)
实际上这些缓冲区使用数组来实现的
比如:ByteBuffer就是用byte[]来实现
缓冲区又分为直接缓冲区和非直接缓冲区
- 非直接缓冲区
直接缓冲区
非直接缓冲区:读取物理磁盘中的某个文件时,会把读到的数据放在内核地址中,然后经过copy到JVM的用户地址空间(写的过程类似)都需要一个copy的步骤
直接缓冲区:将缓冲区建立在物理内存中,通过一个存储在Java堆中的DirectByteBuffer
对象作为这块内存的引用进行操作,避免Java堆和物理内存来回复制数据
缓冲区的字段(属性)
Buffer中有四个重要的属性
// Invariants: mark <= position <= limit <= capacity
//标记索引,通过reset()回到此位置
private int mark = -1;
//操作下一个数据的索引
private int position = 0;
//分界线索引
private int limit;
//缓冲区总容量,因为底层是数组,所以不可变
private int capacity;
缓冲区有读,写两种模式,默认写,使用flip()方法切换成读模式
切换成读模式的时候,之前写到的最后位置就是limit分界线的位置
属性之间需要满足的关系:mark <= position <= limit <= capacity
缓冲区的方法
Buffer的子类提供了操作数据的两个重要方法:
- put():在缓冲区上写数据
- get():在缓冲区上读数据
其中有很多重载方法,方便多种实现
可以通过静态方法allocate()
来创建非直接缓冲区
public static ByteBuffer allocate(int capacity)
通过静态方法allocateDirect()
来创建直接缓冲区只有ByteBuffer有直接缓冲区(只有ByteBuffer有这个方法)
这个DirectByteBuffer
对象就是在堆中操作那块物理内存的引用
public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity);
}
还可以通过FileChannel.map()
通道方法(后文会介绍)来创建DirectByteBuffer
的父类一个MappingByteBuffer
映射字节缓冲区(也是直接缓冲区)
public abstract MappedByteBuffer map(MapMode mode,long position, long size)throws IOException;
//模式,当前位置,总量
//模式:/**只读* Mode for a read-only mapping.*/public static final MapMode READ_ONLY= new MapMode("READ_ONLY");/**读写* Mode for a read/write mapping.*/public static final MapMode READ_WRITE= new MapMode("READ_WRITE");/**写时复制* Mode for a private (copy-on-write) mapping.*/public static final MapMode PRIVATE= new MapMode("PRIVATE");
使用方法isDirect()
来判断此缓冲区是否是一个直接缓冲区
//创建非直接缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//创建直接缓冲区 直接缓冲区只能是Byte
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);System.out.println("buffer.isDirect():buffer是直接缓冲区吗? "+buffer.isDirect());//false
System.out.println("directBuffer.isDirect():buffer是直接缓冲区吗? "+directBuffer.isDirect());//true
深入了解缓冲区的属性与方法
接下来看一段代码,详细的了解缓冲区的属性与方法
import org.junit.Test;import java.nio.Buffer;
import java.nio.ByteBuffer;/*** @author Tc.l* @Date 2020/10/24* @Description: 缓冲区负责数据的存取(boolean类型无, 其他七大数据类型都有)*/
public class _1缓冲区的数据读取 {@Testpublic void test() {//allocate(): 新建一个容量为1024的缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);lookFiled(buffer,"allocate(): 新建一个容量为1024的缓冲区");//put(): 将数据存入缓冲区String s = "abcdef";buffer.put(s.getBytes());lookFiled(buffer,"put(): 将数据abcdef存入缓冲区");//flip() 切换读模式buffer.flip();System.out.println("切换为读模式");//get(): 读缓冲区中的数据byte[] bytes = new byte[buffer.limit()];buffer.get(bytes,0,2);lookFiled(buffer,"get(): 读2个缓冲区中的数据");//读到的数据System.out.print("读到的数据:");for (byte b : bytes) {if (b==0){break;}System.out.print((char)b);}System.out.println();//mark(): 标记位置buffer.mark();System.out.println("mark标记当前位置");//get(): 读缓冲区中的数据buffer.get(bytes,2,2);lookFiled(buffer,"get(): 再读2个缓冲区中的数据");//读到的数据System.out.print("读到的数据:");for (byte b : bytes) {if (b==0){break;}System.out.print((char)b);}System.out.println();//reset(): 恢复到mark标记的位置buffer.reset();lookFiled(buffer,"reset(): 恢复到mark标记的位置");//rewind():回到0位置buffer.rewind();lookFiled(buffer,"rewind():回到0位置");}private void lookFiled(Buffer buffer,String s) {System.out.println();System.out.println("================"+s+"======================");System.out.println("position当前位置:" + buffer.position());System.out.println("limit分界线位置:" + buffer.limit());System.out.println("capacity总容量位置:" + buffer.capacity());if (buffer.hasRemaining()){System.out.println("remaining缓冲区中还可以操作的数量:"+buffer.remaining());}System.out.println();}}
输出内容:
/*
================allocate(): 新建一个容量为1024的缓冲区======================
position当前位置:0
limit分界线位置:1024
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:1024================put(): 将数据abcdef存入缓冲区======================
position当前位置:6
limit分界线位置:1024
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:1018切换为读模式================get(): 读2个缓冲区中的数据======================
position当前位置:2
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:4读到的数据:ab
mark标记当前位置================get(): 再读2个缓冲区中的数据======================
position当前位置:4
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:2读到的数据:abcd================reset(): 恢复到mark标记的位置======================
position当前位置:2
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:4================rewind():回到0位置======================
position当前位置:0
limit分界线位置:6
capacity总容量位置:1024
remaining缓冲区中还可以操作的数量:6
*/
通道(Channel)
通道的结构
通道用于连接IO设备,本身不能直接访问数据,只能与Buffer交互
通道基本都在java.nio.channels包下
- Channel接口四个重要的实现类
- FileChannel 读写操作文件的通道
- DatagramChannel 通过UDP读写网络中的数据通道
- SocketChannel 通过TCP读写网络中的数据
- ServerSocketChannel 服务端监听新来的TCP连接,为每个新连接创建一个SocketChannel
通道的方法
获得通道的三个方式
- 对支持通道的类,提供了
getChannel()
方法 - JDK 7 各个通道提供了open(Path path, OpenOption… options)静态方法
- JDK 7 工具类Files.newByteChannel(Path path, OpenOption… options)
- 对支持通道的类,提供了
通道的读写方法
Channel.read(Buffer) : 读通道的数据写到缓冲区中
- 对于通道来说: 这是在读
- 对于缓冲区来说: 这是在写
Channel.write(Buffer) :读缓冲区的数据写到通道中
- 对于通道来说: 这是在写
- 对缓冲区来说: 这是在读
因为对缓冲区来说在读,所以调用write()方法前,记得使用flip()让缓冲区切换为读模式
通道的read(),write()方法都是以通道作为本身来设计的
深入了解非直接与直接缓冲区的区别
接下来用一段代码进行本地文件的读写操作,并展示缓冲区,通道的使用
非直接与直接缓冲区+通道运输
/*** 通道传输 + 非直接缓冲区* 44秒*/@Testpublic void test1() {LocalDateTime start = LocalDateTime.now();try (//原文件FileInputStream fis = new FileInputStream("D:\\QLDownload\\熊出没·奇幻空间\\熊出没·奇幻空间 熊出没奇幻空间 蓝光(1080P).qlv");//目标文件FileOutputStream fos = new FileOutputStream("D:\\QLDownload\\熊出没·奇幻空间\\2.qlv");//获得连接目标文件的通道FileChannel outChannel = fos.getChannel();//获得连接源文件的通道FileChannel inChannel = fis.getChannel();) {//创建非直接缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);//管道.read(缓冲区): 读管道中的数据写到缓冲区中while ((inChannel.read(buffer) != -1)) {//缓冲区切换为读模式buffer.flip();//管道.write(缓冲区): 读缓冲区的数据写到管道中outChannel.write(buffer);//清空缓冲区buffer.clear();}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}LocalDateTime end = LocalDateTime.now();Duration between = Duration.between(start, end);System.out.println(between.getSeconds() + "秒");}/*** 通道传输 + 直接缓冲区* 我的情况:* 一运行程序,文件夹中的视频就已经复制好了* 但是程序需要JVM去垃圾收集掉直接缓冲区,程序才会结束 所以需要很久** 107秒 无System.gc();* 9秒 有System.gc();* System.gc() 不一定会去垃圾回收*/@Testpublic void test2() {LocalDateTime start = LocalDateTime.now();try (FileChannel inChannel = FileChannel.open(//Path path:地址 OpenOption... options:指定打开文件的方式Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\熊出没·奇幻空间 熊出没奇幻空间 蓝光(1080P).qlv"),//只读StandardOpenOption.READ);FileChannel outChannel = FileChannel.open(Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\2.qlv"),//CREATE_NEW:如果文件存在就报错,不存在就创建(安全)//CREATE:存在就覆盖StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW)) {//通过map(模式,当前位置,总量)方法创建 映射字节缓冲区//MappedByteBuffer是DirectByteBuffer的父类 都可以通过这个引用操作那块物理内存MappedByteBuffer inMappedBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());MappedByteBuffer outMappedBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());byte[] bytes = new byte[inMappedBuffer.limit()];//读源文件管道的直接缓冲区的数据写到bytes中inMappedBuffer.get(bytes);//读bytes中的数据写到目标文件管道的直接缓冲区中outMappedBuffer.put(bytes);} catch (IOException e) {e.printStackTrace();}System.gc();LocalDateTime end = LocalDateTime.now();Duration between = Duration.between(start, end);System.out.println(between.getSeconds() + "秒");}
transferFrom,transferTo 通道运输
还有transferFrom()
和transferTo()
方法直接通道传输,底层帮我们写直接缓冲区
/*** 通道传输* 使用transferFrom和transferTo直接通道传输,底层帮我们写直接缓冲区* 12s*/@Testpublic void test3() {LocalDateTime start = LocalDateTime.now();try (FileChannel inChannel = FileChannel.open(//Path path:地址 OpenOption... options:指定打开文件的方式Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\熊出没·奇幻空间 熊出没奇幻空间 蓝光(1080P).qlv"),//只读StandardOpenOption.READ);FileChannel outChannel = FileChannel.open(Paths.get("D:\\QLDownload\\熊出没·奇幻空间\\2.qlv"),//CREATE_NEW:如果文件存在就报错,不存在就创建(安全)//CREATE:存在就覆盖StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW)) {//inChannel.transferTo(long position, long count,WritableByteChannel target):// 将inChannel管道中position位置,count长度的字节数据 传送到 目标可写字节管道中//inChannel.transferTo(0, inChannel.size(), outChannel);//outChannel.transferFrom(ReadableByteChannel src,long position, long count)//从可读字节管道中position位置,count长度的字节数据 传送到 此管道中//盲猜 方法内部自己使用了直接缓冲区outChannel.transferFrom(inChannel,0,inChannel.size());} catch (IOException e) {e.printStackTrace();}LocalDateTime end = LocalDateTime.now();System.out.println(Duration.between(start, end).getSeconds() + "s");}
分散(Scatter)与聚集(Gather)
分散读取: 一个通道中的数据分散读取到成多个缓冲区中(按照顺序)
聚集写入: 把多个缓冲区的数据聚集写入到一个通道中 (按照顺序)
实际就是用通道的read(),wirte()的重载方法,放进去一个缓冲区数组
public final long read(ByteBuffer[] dsts) throws IOException {return read(dsts, 0, dsts.length);
}public final long write(ByteBuffer[] srcs) throws IOException {return write(srcs, 0, srcs.length);
}
如果是图片的话,不太建议这种方式,只有当第一个缓冲区比图片大时才能运输成功图片,否则打开的不是原来的图片了
@Testpublic void test() {try (//RandomAccessFile(String name文件位置, String mode模式:读,读写等)RandomAccessFile accessInFile = new RandomAccessFile("D:\\Tc.l\\学习\\依赖.txt", "rw");FileChannel channel1 = accessInFile.getChannel();RandomAccessFile accessOutFile = new RandomAccessFile("D:\\Tc.l\\学习\\1.txt", "rw");FileChannel channel2 = accessOutFile.getChannel();) {ByteBuffer buffer1 = ByteBuffer.allocate(100);ByteBuffer buffer2 = ByteBuffer.allocate(1024);ByteBuffer[] buffers = {buffer1,buffer2};//把通道1中的数据 写到多个缓冲区中(按顺序,写满了就换下一个)channel1.read(buffers);//打印多个缓冲区中的数据for (ByteBuffer buffer : buffers) {System.out.println(new String(buffer.array(),0,buffer.limit()));}//把每一个缓冲区都切换成读模式for (ByteBuffer buffer : buffers) {buffer.flip();}//把多个缓冲区中的数据 写到 通道2中channel2.write(buffers);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
字符集
在java.nio.charset.Charset包中
可以拿到编码器,解码器,可以对缓冲区进行编码与解码
@Testpublic void printAvailableCharsets() {//打印所有可用字符集SortedMap<String, Charset> map = Charset.availableCharsets();Set<Map.Entry<String, Charset>> entrySet = map.entrySet();for (Map.Entry<String, Charset> entry : entrySet) {System.out.println(entry.getKey() + "===" + entry.getValue());}}@Testpublic void test() throws CharacterCodingException {Charset charset = Charset.forName("UTF-8");//得到UTF-8编码器CharsetEncoder charsetEncoder = charset.newEncoder();//得到UTF-8解码器CharsetDecoder charsetDecoder = charset.newDecoder();CharBuffer buffer = CharBuffer.allocate(1024);buffer.put("采用UTF-8编码");//切换读模式buffer.flip();//使用UTF-8编码器进行编码 得到字节缓冲区ByteBuffer encodeBuffer = charsetEncoder.encode(buffer);for (int i = 0; i < 17; i++) {System.out.println(encodeBuffer.get());}//使用UTF-8解码器对刚刚编的码 进行 解码(事先需要把刚刚编的码切换模式)encodeBuffer.flip();CharBuffer decodeBuffer = charsetDecoder.decode(encodeBuffer);System.out.println(decodeBuffer.toString());System.out.println("==================================================");//使用GBK解码器对刚刚编的码 进行 解码(发生乱码)Charset gbk = Charset.forName("GBK");CharsetDecoder gbkDecoder = gbk.newDecoder();encodeBuffer.flip();CharBuffer gbkDecodeBuffer = gbkDecoder.decode(encodeBuffer);System.out.println(gbkDecodeBuffer.toString());/*采用UTF-8编码==================================================閲囩敤UTF-8缂栫爜*/}
阻塞与非阻塞
阻塞
传统IO是阻塞式的,某线程调用读写操作时,会被阻塞直到有数据被读取或写入,在被阻塞期间线程不能执行其他任务
因此在网络通信时,服务端要为每一个客户端提供一个线程来处理读写,当客户端数量多时,性能会下降
模拟阻塞式网络通信
@Testpublic void client() {try (//1.获得通道SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));FileChannel inFileChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ)) {//2.指定缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);//3.读取本地文件,发送到服务器while ((inFileChannel.read(buffer)) != -1) {buffer.flip();sChannel.write(buffer);buffer.clear();}//相当于告诉服务端输入结束了sChannel.shutdownOutput();//接受服务器端的反馈int len = 0;while ((len = sChannel.read(buffer))!=-1){buffer.flip();System.out.println(new String(buffer.array(),0,len));buffer.clear();}} catch (IOException e) {e.printStackTrace();}}@Testpublic void server() {try (//1.获得通道ServerSocketChannel ssChannel = ServerSocketChannel.open();FileChannel outFileChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {//2.绑定连接ssChannel.bind(new InetSocketAddress(9898));//3.指定缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);//4.拿到客户端通道SocketChannel sChannel = ssChannel.accept();//5.接受客户端数据,保存到本地while ((sChannel.read(buffer)) != -1) {buffer.flip();outFileChannel.write(buffer);buffer.clear();}//6.给客户端响应buffer.put("服务端接受数据成功".getBytes());buffer.flip();sChannel.write(buffer);//7.关闭客户端通道sChannel.close();} catch (IOException e) {e.printStackTrace();}}
非阻塞
Java NIO是非阻塞的,某线程在某通道调用读写操作时,若没有数据被读取或写入,该线程就会执行其他任务(线程通常把非阻塞IO操作的空间时间用在其他通道上执行IO操作)
因此在网络通信时,NIO可以让服务器用一个或多个线程来处理连接服务器端的所有客户端
选择器(Selector)
前面说过选择器是NIO实现非阻塞式的核心
把客户端通道注册到选择器中
选择器监控这些客户端通道的状况
只有某个通道"准备就绪"了,选择器才把它发送到服务端的一个或多个线程上
Selector
是SelectableChannel
对象的多路复用器
Selector
可以同时监控多个SelectableChannel
对象(利用Selector可以让一个线程管理多个通道)
SelectableChannel
结构
使用
Selector.open()
方法获得选择器Selector selector = Selector.open();
使用
SelectableChannel.register(Selector sel,int ops)
方法来将通道注册到此选择器上//获得通道 ServerSocketChannel ssChannel = ServerSocketChannel.open() //切换为非阻塞模式 ssChannel.configureBlocking(false); //register(Selector sel[注册到哪个选择器上], int ops[监听事件类型]) ssChannel.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey
表示SelectableChannel
和Selector
之间的注册关系- 监听事件类型可用
SelectionKey
的四个常量表示
SelectionKey.OP_READ = 1 << 0
读SelectionKey.OP_WRITE = 1 << 2
写SelectionKey.OP_CONNECT = 1 << 3
连接SelectionKey.OP_ACCEPT = 1 << 4
接收
如果注册时不止一个监听事件可以用逻辑位或
|
连接- 监听事件类型可用
SocketChannel
SocketChannel是一个连接到TCP网络套接字的通道
模拟非阻塞式网络通信
@Testpublic void client() {try (//1.得到通道SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9890))) {//2.切换成非阻塞式sChannel.configureBlocking(false);//3.创建缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);//4.发送文件LocalDateTime now = LocalDateTime.now();DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String s = now.format(formatter);buffer.put(s.getBytes());buffer.flip();sChannel.write(buffer);buffer.clear();} catch (IOException e) {e.printStackTrace();}}@Testpublic void server() {try ( //1.获得通道ServerSocketChannel ssChannel = ServerSocketChannel.open()) {//2.切换成非阻塞式ssChannel.configureBlocking(false);//3.绑定连接ssChannel.bind(new InetSocketAddress(9890));//4.获取选择器Selector selector = Selector.open();//5.把通道注册到选择器上,并指定监听事件//register(Selector sel[注册到哪个选择器上], int ops[SelectionKey属性常数])ssChannel.register(selector, SelectionKey.OP_ACCEPT);//6.轮询式获取选择器上已经准备就绪的事件while (selector.select() > 0) {//7.获得当前选择器中所有注册的 选择键(就绪的监听事件)Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {//8.获取准备就绪的事件SelectionKey key = iterator.next();//9.判断具体是什么事件准备就绪if (key.isAcceptable()) {//10.接受就绪事件 获得客户端SocketChannel sChannel = ssChannel.accept();//11.切换非阻塞式sChannel.configureBlocking(false);//12.把该通道注册到选择器上sChannel.register(selector, SelectionKey.OP_READ);} else if (key.isReadable()) {//13. 获得当前选择器上"读就绪"的通道SocketChannel sChannel = (SocketChannel) key.channel();//14.读取数据ByteBuffer buffer = ByteBuffer.allocate(1024);int len = 0;while ((len = sChannel.read(buffer)) != -1) {buffer.flip();System.out.println(new String(buffer.array(), 0, len));buffer.clear();}}//15.取消选择键iterator.remove();}}} catch (IOException e) {e.printStackTrace();}}
DetagramChannel
DatagramChannel是一个能收发UDP包的通道
@Testpublic void client() {try (DatagramChannel dc = DatagramChannel.open()) {dc.configureBlocking(false);ByteBuffer buffer = ByteBuffer.allocate(1024);LocalDateTime now = LocalDateTime.now();buffer.put(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).getBytes());buffer.flip();dc.send(buffer, new InetSocketAddress("127.0.0.1", 8090));} catch (IOException e) {e.printStackTrace();}}@Testpublic void server() {try (DatagramChannel dc = DatagramChannel.open()) {dc.configureBlocking(false);Selector selector = Selector.open();dc.bind(new InetSocketAddress(8090));dc.register(selector, SelectionKey.OP_READ);while(selector.select()>0){Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()){SelectionKey key = iterator.next();if (key.isReadable()) {ByteBuffer buffer = ByteBuffer.allocate(1024);dc.receive(buffer);buffer.flip();System.out.println(new String(buffer.array(),0,buffer.limit()));buffer.clear();}}iterator.remove();}} catch (IOException e) {e.printStackTrace();}}
Pipe
Pipe是管道,用于2个线程间单向数据连接
从Pipe的sink通道写入,从source通道读取
@Testpublic void test1(){try {Pipe pipe = Pipe.open();ByteBuffer buffer = ByteBuffer.allocate(1024);//发送数据Pipe.SinkChannel sinkChannel = pipe.sink();buffer.put(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH-mm-ss")).getBytes());buffer.flip();sinkChannel.write(buffer);//接受数据Pipe.SourceChannel sourceChannel = pipe.source();ByteBuffer allocate = ByteBuffer.allocate(1024);int len = sourceChannel.read(allocate);allocate.flip();System.out.println(new String(allocate.array(),0,len));//关闭sinkChannel.close();sourceChannel.close();} catch (IOException e) {e.printStackTrace();}}
学习视频:尚硅谷NIO
如有错误,麻烦指出,感谢~
一篇通俗易懂的文章初探NIO相关推荐
- xnio java_java基础篇---新I/O技术(NIO)
在JDK1.4以前,I/O输入输出处理,我们把它称为旧I/O处理,在JDK1.4开始,java提供了一系列改进的输入/输出新特性,这些功能被称为新I/O(NEW I/O),新添了许多用于处理输入/输出 ...
- 转载一篇不错的文章:谈谈“野生”Java程序员学习的道路
逛论坛看到的一篇不错的文章,特此转载:本文转载自:左潇龙 原文链接:点击打开链接 引言 很尴尬的是,这个类型的文章其实之前笔者就写过,原文章里,笔者自称LZ(也就是楼主,有人说是老子的简写,笔者只想 ...
- 海纳百川而来的一篇相当全面的Java NIO教程
目录 零.NIO包 一.Java NIO Channel通道 Channel的实现(Channel Implementations) Channel的基础示例(Basic Channel Exampl ...
- 【操作系统】IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析
[操作系统]IO模型篇之从BIO.NIO.AIO到内核select.epoll剖析! 参考资料: Java网络编程-IO模型篇 [Redis]网络模型:Redis的IO多路复用 [操作系统]全面解析I ...
- 关于元数据,全网最通俗易懂的文章!
" 荐:这篇文章出至我的前同事--90后美女程序员(龚菲).关于元数据.元数据管理的文章有很多,但要说是最通俗易懂的还得是这一篇. 本文原文在这里:<关于元数据,全网最通俗易懂的文章! ...
- NBT-19年2月刊4篇35分文章聚焦宏基因组研究
新年4篇35分文章聚焦宏基因组研究 Nature Biotechnology (NBT,自然生物技术,IF 35.7)在2019年2月刊(https://www.nature.com/nbt/volu ...
- NBT-新年4篇35分文章聚焦宏基因组研究
文章目录 新年4篇35分文章聚焦宏基因组研究 1. 超高速细菌基因组检索技术 摘要 序列搜索方法 2. 宏基因组中设计全面可扩展探针捕获序列多样性 摘要 CATCH设计探针 3. 1520个人类肠道可 ...
- 深度学习概述:从感知机到深度网络(找到一篇大牛的文章快围观)
https://www.toutiao.com/i6652554938519912968/ 2019-01-31 15:10:48 前言 别说我不宠粉,分享一篇大佬的文章,没办法书读得少不知道怎么去形 ...
- 学习笔记:The Log(我所读过的最好的一篇分布式技术文章
学习笔记:The Log(我所读过的最好的一篇分布式技术文章) 前言 这是一篇学习笔记. 学习的材料来自Jay Kreps的一篇讲Log的博文. 原文很长,但是我坚持看完了,收获 ...
- 我历时3年才写了10余篇源码文章,但收获了100w+阅读
你好,我是若川.最近来了一些读者朋友,在这里简单介绍自己的经历,也许对你有些启发.之前发过这篇文章,现在修改下声明原创,方便保护版权. 最近组织了源码共读活动1个月,200+人,一起读了4周源码,很多 ...
最新文章
- 破纪录了!用 Python 实现自动扫雷!
- android系统属性获取及设置
- 【AI】人工智能深度学习入门路线
- 原生Java代码拷贝目录
- JS小功能(操作Table--动态添加删除表格及数据)实现代码
- Nginx源码分析(3)
- 双向队列(STL做法)
- 3.Excel数据透视表
- C++11新特性之八——函数对象function
- WinSock I/O 模型 -- IOCP 模型
- mongodb数据库的安装 for windows版本 0916
- 高通宣布与华为达成新专利授权协议,华为砸下18亿美元
- awk处理带有空格的字符串_五分钟入门文本处理三剑客grep awk sed
- 设计一台模型计算机 实现下列指令系统,基本模型机的设计与实现1
- python基础之Day20part1
- elasticsearch批量数据导入和导出
- java时间轮定时器_基于时间轮的定时器
- linux文件增加自定义属性,Linux 笔记...文件和目录属性useradd、userdel、usermod 、passwd...
- 常用数列总结性质记录
- 世界著名的十大定律,你都知道吗