通道和缓冲区
java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到IO设备(例如:文件、套接字)的连接。若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区,对数据进行处理。
简而言之,Channel负责传输,Buffer负责存储
缓冲区
在Java NIO中负责数据的存取,缓冲区就是数组。用于存取不同数据类型的数据。
根据数据类型不同(boolean以外),提供了相应类型的缓冲区:ByteBuffer 、CharBuffer、ShortBuffer、IntBuffer等。
上述缓冲区的管理方式几乎一致,通过allocate()获取缓冲区
缓冲区存取数据的两个核心方法
-
put():存入数据到缓冲区
-
get():从缓冲区取出数据
缓冲区中的四个核心属性
- capacity:容量,表示缓冲区中最大存储数据的容量,一旦声明,不能改变
- limit:界限,表示缓冲区中可以操作数据的大小。limit后数据不能进行读写
- position:位置,表示缓冲区中正在操作数据的位置
- mark:标记,表示记录当前position的位置。可以通过reset()恢复到mark位置
0<=mark<=position <= limit <= capacity
put()向缓冲中放入数据,position随之移动。
flip()切换为读 模式,limit移动到position的位置,position回到起点。
get(),position向后读取,直到limit。
rewind():可重复读,position回到起点,limit不变。
clear():清除缓冲区。但是缓冲区的数据依然存在,但是处于“被遗忘”状态
mark():记录当前position的位置
reset():恢复到mark的位置
直接缓冲区与非直接缓冲区
非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中
直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率。
非直接缓存区是在用户地址空间进行建立缓冲区,在将用户地址空间的数据拷贝到内核地址空间,然后再读写物理磁盘。直接缓冲区则是直接在内存映射文件读写,不用再拷贝。
但是内存映射文件并不受JVM控制,并且创建和销毁开销较大。一般,最好仅在直接缓冲区在程序性能方面带来明显好处时分配它们。
通道(channel)
表示IO源于目标打开的连接。类似于流,不过channel本身不能直接访问数据,Channel只能与Buffer进行交互。在Java NIO中负责缓冲区中数据的传输。
通道的主要实现类
- FileChannel
- SocketChannel
- ServerSocketChannel
- DatagramChannel
获取通道
1.通过getChannel()方法
本地IO:
-
FileInputStream/FileOutputStream
-
RandomAccessFile
网络IO:
- socket
- ServerSocket
- DatagramSocket
2.在JDK1.7中NIO.2 针对各个通道提供了静态方法open()
3.在JDK1.7中的NIO.2 的Files工具类的newByteChannel()
通道之间的数据传输
transferFrom()
transferTo()
也是通过直接缓冲区
分散(Scatter)与聚集(Gather)
分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
聚集写入(Gathering Writes):将多个缓冲区的数据聚集到通道
就是使用缓冲区数组进行读写操作
阻塞与非阻塞
针对于网络通信。
@Test
public void client() throws IOException {
//1.获取通道
SocketChannel channel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2.配置非阻塞
channel.configureBlocking(false);
//3.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//4.向服务器发送数据
buf.put(LocalDateTime.now().toString().getBytes());
buf.flip();
channel.write(buf);
//5.关闭通道
channel.close();
}
@Test
public void server() throws IOException {
//1.获取通道
ServerSocketChannel channel = ServerSocketChannel.open();
//2.配置非阻塞
channel.configureBlocking(false);
//3.绑定连接
channel.bind(new InetSocketAddress(9898));
//4.获取选择器
Selector selector = Selector.open();
//5.将通道注册到选择器,并指定监听接收事件
channel.register(selector, SelectionKey.OP_ACCEPT);
while(selector.select()>0){
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while(it.hasNext()){
SelectionKey key = it.next();
//判断具体是什么事件就绪
if(key.isAcceptable()){
//获取连接
SocketChannel client = channel.accept();
//非阻塞
client.configureBlocking(false);
client.register(selector,SelectionKey.OP_READ);
}else if(key.isReadable()){
//读就绪,从通道中读取数据
SocketChannel channel1 = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
while(channel1.read(buf)!=-1){
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
buf.clear();
}
}
//取消选择键
it.remove();
}
}
}
管道
Java NIO管道是2个线程之间的单向数据连接。Pipe有一个soure通道和一个sink通道。数据会被写到sink通道,从source通道读取。
@Test
public void test() throws IOException {
//获取管道
Pipe pipe = Pipe.open();
//分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//写入数据
Pipe.SinkChannel sink = pipe.sink();
buf.put("向管道中存入数据".getBytes());
buf.flip();
sink.write(buf);
buf.clear();
//读取数据
Pipe.SourceChannel source = pipe.source();
source.read(buf);
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
source.close();
sink.close();
}