一.缓冲区 Buffer
1 简介
(1) 缓冲区其实就是一个数组,在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,它也是写入到缓冲区中的;
(2)在NIO中,所有的缓冲区类型都继承于抽象类 Buffer,最常用的就是ByteBuffer,对于Java中的基本类型,基本都有一个具体Buffer类型与之相对应,它们之间的继承关系如下图所示:
常用方法(以 IntBuffer 为例)
//分配新的内存空间
//新的缓冲区的当前位置position为0,其界限(限制位置)limit为其容量。
IntBuffer buffer =InBuffer.allocate(8);
//或者包装一个缓冲区
int array[] = new int[8];
IntBuffer buffer = IntBuffer.wrap(array);
---------------------------------------------------------------------------
//获取当前缓冲区的最大数据容量capacity = 8
buffer.capacity()
----------------------------------------------------------------------------
//将数据3写入缓冲区当前位置,当前位置position递增
buffer.put(3)
//读取此缓存区当前位置的整数,然后当前位置递增
buffer.get();
-----------------------------------------------------------------------------
//获取当前位置position值
buffer.position();
//设置当前位置值为1
buffer.position(1);
------------------------------------------------------------------------------
//重设此缓冲区,将限制设置limit为当前位置,然后当前位置position为0
buffer.flip()
------------------------------------------------------------------------------
//查看在当前位置和限制位置之间是否有元素,有返回true
buffer.hasRemaining()
------------------------------------------------------------------------------
//读取此缓存区限制limit值
buffer.limit();
//设置当前limit 值为2
buffer.limit(2);
2. 基本原理
缓冲区对象本质上是一个特殊的数组,对象内置了一些机制(最重要的属性有下面三个),能够跟踪和记录比如get(),put()等方法引起的 缓冲区的状态变化。以下三个属性值之间有一些相对大小的关系:0<=position<=limit <=capacity.
position: 指定下一个将要被写入或者读取的元素索引,它的值由get0/put0方法自动更新,在新创建一个Buffer 对象时,position被初始化为0.
limit: 指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。在初始化后 limit = 分配的内存空间容量
capacity: 指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。在初始化后capacity的值不会再发生变化
3. 缓冲区分片
现有缓冲区上可以通过 slice() 方法切出来一个新的缓冲区,子缓冲区相当于是现有缓冲区的一个试图窗口,与现有缓冲区在底层数据层面上数据共享(子缓冲区修改数据,原缓冲区的数据也会发生变化)。
public class BufferSlice{
//初始化原缓冲区
ByteBuffer buffer = ByteBuffer.allocate(20);
for(int i=0;i<buffer.capacity();i++){
buffer.put((byte)i );
}
//创建子缓冲区
buffer.position(3);
buffer.limit(13);
ByteBuffer slice = buffer.slice();
}
4. 只读缓冲区
通过调用.asReadOnlyBuffer() 方法,可以产生一个与原来缓冲区完全相同的缓冲区,并与原缓冲区共享数据。只不过它是只读的。如果原缓冲区的内容发生变化,只读缓冲区的内容也随之发生变化。
5. 直接缓冲区
直接缓冲区是为加快I/O速度,使用一种特殊方式为其分配内存的缓冲区。java虚拟机将尽最大努力直接对它执行本机I/O操作。创建直接缓冲区时用 allocateDirect。使用方式和普通缓冲区无区别。
6. 内存映射
内存映射是一种读和写文件数据的方法,比常规的基于流和基于通道的 I/O 快的多。内存映射是通过将文件中实际读取或者写入的部分映射到内存中实现的。
static private final int start = 0;
static private final int size=1024;
public static void main(String[] args)throws Exception {
//RandomAccessFile 提供了四种模式
//r 以只读方式打开文本
//rw 可以读和写
//rws 每当进行写操作,同步刷新到磁盘,刷新内容和元数据
//rwd 每当进行写操作,同步刷新到磁盘,刷新内容
RandomAccessFile raf = new RandomAccessFile("H://test.txt","rw");
FileChannel fc = raf.getChannel();
//把缓冲区跟文件系统进行一个映射关联
//只要操作缓冲区里面的内容,文件内容也会跟着改变
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
mbb.put(0, (byte) 97);
mbb.put(1023, (byte) 122);
raf.close();
}
二.选择器Selector
1. 作用:
传统的 Server/Client 模式会基于TPR,服务器会为每个客户端请求建立一个线程,由该线程单独负责处理一个客户请求。这种模式带来的一个问题就是线程数量的剧增,增大服务器的开销。使用线程池并设置最大连接数,可以避免这个问题,但会出现,当所有线程都进行比较耗时的处理时,新来的请求无论多简单,都只能等待。
NIO中非阻塞 I/O 采用基于Reactor 模式的工作方式,I/O 调用不会被阻塞,相反是注册感兴趣的特定 I/O 事件,在发生特定事件时,系统再通知我们。Selector 就是注册各种 I/O 事件的地方,当那些事件发生的时候,就是Selector 告诉我们所发生的事件。如下图当有读或者写等任何注册的事件发生时,可以从Selector中获得相应的 SelectorKey,同时 SelectorKey 中可以找到发生的事件和该事件所发生的具体 SelectableChannel, 以获得客户端发送过来的数据。
2. 使用
使用NIO 中非阻塞I/O编写服务器处理程序,大体上可以分为下面三个步骤:
a.向Selector对象注册感兴趣的事件。
b.从Selector中获取感兴趣的事件。
c.根据不同的事件进行相应的处理。
private Selector getselector() throws IOException {
//创達Selector 对象
selector = Selector.open();
//创建可选择通道,并配置为非阻塞模式
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
//绑定通道到指定端口
ServerSocket socket = server.socket();
InetSocketAddress address = new InetSocketAddress(8088);
socket.bind(address);
//向Selector 中注册感兴题的事件
//OP_ACCEPT:新的连接发生时所产生的事件 OP_CONNECT OP_WRITE OP_READ
server.register(selector, SelectionKey.OP_ACCEPT);
return selector;
}
/*
监听
* */
public void listen() {
try {
while (true) {
//该调用会阻塞,直到至少有一个事件发生
selector = getselector();
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = (SelectionKey) iter.next();
iter.remove();
process(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/* 根据不同的事件进行处理*/
private void process(SelectionKey key) throws IOException {
//接收请求
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
}
//读信息
else if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
int len = channel.read(buffer);
if (len > 0) {
buffer.flip();
String content = new String(buffer.array(), 0, len);
SelectionKey skey = channel.register(selector, SelectionKey.OP_WRITE);
skey.attach(content);
} else {
channel.close();
buffer.clear();
}
//写事件
} else if (key.isWritable()) {
SocketChannel channel = (SocketChannel) key.channel();
String content = (String) key.attachment();
ByteBuffer block = ByteBuffer.wrap(("输出内容:" + content).getBytes());
if (block != null) {
channel.write(block);
} else {
channel.close();
}
}
}
三.通道Channel
1.基本概念
通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口。它们之间的继承关系如下图所示
2.使用NIO读取/写入数据:
1) 从FilelnputStream获取Channel 2) 创建Buffer 3) 将数据从Channel读取/写入到Buffer中 4)从Buffer中读取/写入数据
/*读取数据 */
public static void main(String[] args) throws Exception {
FileInputStream fin = new FileInputStream("E://test.txt");
//获取通道
FileChannel fc = fin.getChannel();
//创建媛冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取数据到缓冲区
fc.read(buffer);
buffer.flip();
while (buffer.remaining() > 0) {
byte b = buffer.get();
System.out.print(((char) b));
fin.close();
}
}
public static void main(String[] args) throws Exception {
byte message[] = { 83, 111, 109, 101,32,98,121,116,101,115,46 };
FileOutputStream fout =new FileOutputStream("E://test.txt");
FileChannel fc=fout.getChannel();
ByteBuffer buffer =ByteBuffer.allocate(1024);
for (int i=0; i<message.length;++i) {
buffer.put(message[i]);
}
buffer.flip();
fc.write( buffer);
fout.close();
}