Java NIO
是 Java non-blocking IO 。 从jdk 1.4开始,Java提供了一系列改进的 I/O 的新特性,被统称为 NIO,是同步非阻塞的 .
NIO相关的类都被放在 java.nio
包及子包下。
三大核心:
Channel
(通道)、Buffer
(缓冲区)、Selector
(选择器)
Buffer :
四个属性:
capacity
: 容量,表示缓冲区中最大存储数据的容量。一旦声明不能更改。
limit
:界限,表示缓冲区中可以操作数据的大小。(limit 后的数据不能进行读写)
position
: 位置,表示缓冲区中正在操作数据的位置。
mark
: 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置。
测试:
public class test_Buffer {
public static void main(String[] args) {
CharBuffer charBuffer = CharBuffer.allocate(1024);
String s="abcde";
charBuffer.put(s.toCharArray());
System.out.println(charBuffer.position()); // 5
System.out.println(charBuffer.capacity()); // 1024
System.out.println(charBuffer.limit()); //1024
System.out.println("读写切换------------------------");
charBuffer.flip(); //这里改成了读
System.out.println(charBuffer.position()); //0 ,从第0个开始读起
System.out.println(charBuffer.limit()); //5 ,只有前五个有内容
char[] bytes= new char[charBuffer.limit()];
charBuffer.get(bytes);
System.out.println(new String(bytes));
System.out.println(charBuffer.position()); //5 读到了第五个位置
System.out.println(charBuffer.limit()); //5 只有前五个有内容
charBuffer.clear(); //清除之后就默认变回 "写" ,因为根本不存在数据可以读
System.out.println(charBuffer.position()); // 0
System.out.println(charBuffer.limit()); //1024
}
}
Channel :
通道(channel): Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的流,只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。
主要实现类:
FileChannel
(文件), SocketChannel
(TCP源) ,ServerSocketChannel
,DatagramChannel
(UDP源)
Channel可以理解成原来的流 。
测试
public class test_channel {
public static void main(String[] args) throws Exception {
// writeChannel();
readChannel();
}
public static ByteBuffer readChannel() throws IOException {
FileInputStream inputStream=new FileInputStream(first.class.getClassLoader().getResource("a.txt").getPath());
FileChannel channel = inputStream.getChannel();
ByteBuffer byteBuffer= ByteBuffer.allocate(1024);
channel.read(byteBuffer); //从文件中读取内容
byteBuffer.flip(); //切换为读
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
byte[] bytes=new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
System.out.println(new String(bytes));
channel.close();
inputStream.close();
return byteBuffer;
}
public static void writeChannel() throws Exception {
FileInputStream inputStream=new FileInputStream(first.class.getClassLoader().getResource("a.txt").getPath());
FileChannel Rchannel = inputStream.getChannel();
ByteBuffer byteBuffer= ByteBuffer.allocate(1024);
Rchannel.read(byteBuffer); //从文件中读取内容
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
byteBuffer.put("新写入的内容".getBytes());
System.out.println(byteBuffer.position());
FileOutputStream outputStream=new FileOutputStream(first.class.getClassLoader().getResource("a.txt").getPath());
FileChannel channel = outputStream.getChannel();
byteBuffer.flip(); //读模式,channel根据 position 和limit将缓冲区的数据写入到文件
System.out.println("读模式下的 position为"+byteBuffer.position()+",limit为"+byteBuffer.limit());
channel.write(byteBuffer);
channel.close();
Rchannel.close();
inputStream.close();
outputStream.close();
}
}
从上面的测试代码,我们可以发现,从输入流得到的Channel就只能将数据写到缓冲区,从输出流得到的Channel只能从缓冲区读出数据
Selector(仅网络编程Socket):
Java NIO
能实现非阻塞的 IO方式。就是使用了Selector
,可以用一个线程处理多个客户端的连接
当然,用selector实现非阻塞,channel本身也必须是非阻塞的,像文件的channel不能变成非阻塞,所以无法使用selector
Selector可以检测到多个注册的Channel
上是否有事件发生(多个 Channel
以事件的方式可以注册到同一个 Selector
),如果有事件发生,便获取事件然后针对每个事件进行相应的处理,这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
注意,Selector 只有套接字可以用,也就是 ServerSocketChannel 才可以用
- 当客户端连接时,会通过 ServerSocketChannel 得到 SocketChannel。
- Selector 进行监听 select 方法,返回有事件发生的通道的个数。
- 将 socketChannel 注册到 Selector 上,register(Selector sel, int ops),一个 Selector 上可以注册多个 SocketChannel。
- 注册后返回一个 SelectionKey,会和该 Selector 关联(集合)。
- 进一步得到各个 SelectionKey(有事件发生)。
- 在通过 SelectionKey 反向获取 SocketChannel,方法 channel()。
- 可以通过得到的 channel,完成业务处理。
测试
public class test_Selector {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel channel1 =ServerSocketChannel.open();
channel1.bind(new InetSocketAddress(8080));
channel1.configureBlocking(false); //必须设置非阻塞才能使用selector
SelectionKey key1 = channel1.register(selector, SelectionKey.OP_ACCEPT);
ServerSocketChannel channel2 =ServerSocketChannel.open();
channel2.bind(new InetSocketAddress(8081));
channel2.configureBlocking(false); //必须设置非阻塞才能使用selector
SelectionKey key2 = channel2.register(selector, SelectionKey.OP_ACCEPT);
while(true){
int select = selector.select();
if(select>0){//说明有事件处理
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//取出每一个socket来查看
while (iterator.hasNext()){
SelectionKey cur = iterator.next();
if(cur.isAcceptable()){ // 因为注册时使用 OP_ACCEPT 进行注册,所以只能判断是否有 accept事件
if(cur == key1){
System.out.println("8080端口有人连接");
SocketChannel accept = channel1.accept();
accept.configureBlocking(false);
accept.register(selector,SelectionKey.OP_READ); //注册读事件
System.out.println(accept.getRemoteAddress()+"上线了");
}
if(cur == key2){
System.out.println("8081端口有人连接");
}
}else if(cur.isReadable()){ // 有channel注册读事件,那么此时就说明有通道是可读的
SocketChannel socketChannel = (SocketChannel) cur.channel();
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
socketChannel.read(byteBuffer);
System.out.println("接收到数据了");
System.out.println(new String(byteBuffer.array()).trim());
byteBuffer.clear();
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.position());
socketChannel.write(ByteBuffer.wrap("服务器已经收到了".getBytes())); //注意发送消息时,缓冲区必须要使用wrap方法创建
}
iterator.remove();
//防止重复处理事件
}
}
}
}
}
public class client {
public static void main(String[] args) throws IOException, InterruptedException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(8080));
ByteBuffer byteBuffer = ByteBuffer.wrap("hello,server".getBytes()); //注意发送消息时,缓冲区必须要使用wrap方法创建
System.out.println("客户端发送消息");
socketChannel.write(byteBuffer);
ByteBuffer recv = ByteBuffer.allocate(1024);
socketChannel.read(recv);
System.out.println("客户端收到:"+new String(recv.array()).trim());
Thread.sleep(50000);
socketChannel.close();
}
}