java-NIO相关内容
1.NIO概述
NIO全称Java Non-blocking lO或Java New lO,是从JDK1.4开始引入的一套新的IO,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。
BIO、NIO、AIO(NIO2)
- BIO 阻塞式
- NIO 非阻塞式
- JDK1.7后 新增AIO (Asynchronous IO) 异步IO
IO操作模式:
- PIO(Programing IO)
所有的IO操作由CPU处理
,CPU占用率比较高。
- DMA(Direct Memory Access) 直接内存访问 硬件实现
CPU把IO操作控制权交给DMA控制器
,只能以固定的方式读写,CPU空闲做其它工作。
- 通道方式(Channel)硬件实现
- 能执行有限通道指令的IO控制器,代替CPU管理控制外设。
- 通道有自己的指令系统,是一个协处理器,具有更强的独立处理数据输入和输出的能力。
为了配合硬件的发展,新增NIO。
[wppay]
2.NIO组成
NIO三个核心部分:
- Buffer:缓冲区
- Channel:通道
- Selector:选择器(轮询器) 实现多路复用网络编程
NIO和普通IO的区别:
3.Buffer缓冲区
Java NIO中的Buffer用于和NIO通道进行交互。
缓冲区本质上是一块可以写入数据,也可以从中读取数据的内存。
Buffer是一个抽象类,子类包含:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
由于子类也是抽象类,所以不能实例化对象。
4.Buffer基本使用步骤
- 1.创建缓冲区
- 间接缓冲区:
- 在堆中开辟,易于管理,垃圾回收器可以回收,空间有限,读写文件速度较慢。
- 直接缓冲区:
- 直接
在物理内存中开辟空间
,空间比较大,读写文件速度快 - 缺点:
不受垃圾回收器控制,创建和销毁耗性能
。
- 直接
- 间接缓冲区:
- 2.调用put方法写入数据到Buffer
- 3.调flip()方法
- 4.调用get方法从Buffer中读取数据
- 5调用clear()方法或者compact()方法
5.课堂案例
public class ByteBufferDemo {
public static void main(String[] args) {
// 1创建缓冲区
// 底层实现
// allocate -> HeapByteBuffer -> super -> final byte[] hb;
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 2添加数据
byte[] data = "helloworld".getBytes();
buffer.put(data);
// 3反转buffer(把写入模式改成读取模式)
buffer.flip();
// 4获取数据
// for(int i=0;i<buffer.limit();i++) {
// byte b = buffer.get();
// System.out.print((char)b);
// }
byte[] data2 = new byte[buffer.limit()];
buffer.get(data2);
System.out.println(new String(data2));
// 5清空buffer
buffer.clear();
}
}
6.Buffer核心方法
7.Buffer原理
Buffer包括三个重要属性:
- capacity
- position
- limit
public class ByteBufferDemo2 {
public static void main(String[] args) {
// 1创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println(
"放数据前位置:" + buffer.position() + " 限制:" + buffer.limit() + " 容量:" + buffer.capacity());
// 2添加数据
byte[] data = "helloworld".getBytes();
buffer.put(data);
System.out.println(
"放数据后位置:" + buffer.position() + " 限制:" + buffer.limit() + " 容量:" + buffer.capacity());
// 3反转buffer(把写入模式改成读取模式)
buffer.flip();
System.out.println(
"反转位置:" + buffer.position() + " 限制:" + buffer.limit() + " 容量:" + buffer.capacity());
// 4获取数据
// for(int i=0;i<buffer.limit();i++) {
// byte b = buffer.get();
// System.out.print((char)b);
// }
byte[] data2 = new byte[buffer.limit()];
buffer.get(data2);
System.out.println(new String(data2));
System.out.println(
"读完位置:" + buffer.position() + " 限制:" + buffer.limit() + " 容量:" + buffer.capacity());
// 5清空buffer
buffer.clear();
System.out.println(
"清空之后位置:" + buffer.position() + " 限制:" + buffer.limit() + " 容量:" + buffer.capacity());
}
}
rewind()与compact()方法实践
rewind():pos指针返回头部可以重新读取。
compact():将未读的元素整理压缩到前部。
public class ByteBufferDemo3 {
public static void main(String[] args) {
// 1创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 2添加数据
byte[] data = "helloworld".getBytes();
buffer.put(data);
// 3反转buffer(把写入模式改成读取模式)
buffer.flip();
byte[] data2 = new byte[5];
buffer.get(data2);
System.out.println(new String(data2));
System.out.println(
"读取5个之后位置:" + buffer.position() + " 限制:" + buffer.limit() + " 容量:" + buffer.capacity());
// System.out.println("重绕");
// buffer.rewind();// 重绕缓冲区
// buffer.get(data2);
// System.out.println(new String(data2));
// 5清空buffer
// buffer.clear();
buffer.compact(); // 整理压缩
// System.out.println("清空之后位置:"+buffer.position()+" 限制:"+buffer.limit()+"
// 容量:"+buffer.capacity());
System.out.println(
"compact之后位置:" + buffer.position() + " 限制:" + buffer.limit() + " 容量:" + buffer.capacity());
buffer.flip();
buffer.get(data2);
System.out.println(new String(data2));
}
}
8.Channel通道
Channel类似流。数据可以从Channel读到Buffer中,也可以从Buffer写到Channel中。
9.Channel相关API
Channel接口主要实现类:
- FileChannel 读写文件通道TCP
- ServerSocketChannel 侦听套接字通道TCP
- SocketChannel 套接字通道UDP
- DatagramChannel 数据包通道UDP
FileChannel
- java nio 中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。
- FileChannel无法设置为非阻塞模式
- 创建方式有三种:
- 使用文件字节流或RandomAccessFile来获取一个FileChannel实例。
- 使用Channels工具类
- JDK1.7之后才能使用,FileChannel.open()方法。
FileChannel读写文件
public class FileChannelDemo {
public static void main(String[] args) throws Exception {
// write();
read();
}
public static void write() throws Exception {
// 1 创建FileChannel
// 1.1使用文件字节流或RandomAccessFile来获取一个FileChannel实例。
// FileOutputStream fos=new FileOutputStream("d:\\out.txt");
// FileChannel channel = fos.getChannel();
// RandomAccessFile raf=new RandomAccessFile("d:\\out.txt", "rw");
// FileChannel channel = raf.getChannel();
// 1.2 使用Channels工具类
// FileChannel channel = (FileChannel) Channels.newChannel(new FileOutputStream("d:\\out.txt"));
// JDK1.7之后才能使用, FileChannel.open()方法。
FileChannel channel =
FileChannel.open(Paths.get("d:\\out.txt"), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
// 2 写入文件
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("helloworld".getBytes()); // position 10 limit 1024 capaicy1024
buffer.flip();
channel.write(buffer);
// 3关闭
channel.close();
}
//读取存在乱码问题 GBK中文2个字节,当缓冲区为5时出现乱码
public static void read() throws Exception {
// 1创建FileChannel
FileChannel channel = new RandomAccessFile("d:\\out.txt", "r").getChannel();
// 2读取
// 创建解码器
CharsetDecoder decoder = Charset.forName("gbk").newDecoder();
// 创建字符缓冲区
CharBuffer charBuffer = CharBuffer.allocate(5);
ByteBuffer buffer = ByteBuffer.allocate(5);
while (channel.read(buffer) > 0) {
buffer.flip();
// 解码
decoder.decode(buffer, charBuffer, false);
charBuffer.flip();
System.out.println(charBuffer.toString());
buffer.compact(); // 压缩整理
charBuffer.clear();
}
// 3关闭
channel.close();
}
}
复制文件
public class CopyFile {
public static void main(String[] args) throws Exception {
// 使用NIO实现copy
// 1 创建FileChannel
FileChannel readChannel = new RandomAccessFile("d:\\图片\\copy.jpg", "r").getChannel();
FileChannel writeChannel = new RandomAccessFile("d:\\222.jpg", "rw").getChannel();
// 2读取和写入(间接缓冲区,堆)
// ByteBuffer buffer=ByteBuffer.allocate(1024);
// 直接缓冲区 在直接内存中(物理空间)
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (readChannel.read(buffer) > 0) {
buffer.flip();
writeChannel.write(buffer);
buffer.clear();
}
// 3关闭
readChannel.close();
writeChannel.close();
System.out.println("复制完毕");
}
}
间接缓冲区
直接缓冲区
10.内存映射文件
直接缓冲区的使用,可以提高读写的速度。但是直接缓冲区的创建和销毁的开销比较大,一般大文件操作或能显著提高读写性能时使用。
内存映射文件也属于直接缓冲区。
注意:如果文件超过2G,需要分多个文件映射。
public class MappedByteBufferDemo {
public static void main(String[] args) throws Exception{
//使用内存映射文件复制
//1 创建FileChannel
FileChannel readChannel=FileChannel.open(Paths.get("d:\\111.wmv"), StandardOpenOption.READ);
FileChannel writeChannel=FileChannel.open(Paths.get("d:\\222.wmv"), StandardOpenOption.CREATE,StandardOpenOption.WRITE);
//2 映射(直接内存)
MappedByteBuffer map = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, readChannel.size());
writeChannel.write(map);
//3关闭
readChannel.close();
writeChannel.close();
System.out.println("执行完毕");
}
}
11.NIO实现网络编程
- 传统的网络编程是一种阻塞模式,在一些任务小,高并发情况下效率不高。
- 在NIO中提供了实现非阻塞式网络编程API:
- ServerSocketChannel
- ServerSocketChannel是一个基于通道的socket监听器,等同于ServerSocket类。
- SocketChannel
- SocketChannel是一个基于通道的客户端套接字,等同于Socket类。
阻塞式网络编程
服务端:
public class TcpServer {
public static void main(String[] args) throws Exception {
//1创建ServerSocketChannel
ServerSocketChannel listener=ServerSocketChannel.open();
//2绑定IP地址和端口号
listener.bind(new InetSocketAddress("10.9.62.184", 8888));
//3侦听、阻塞
System.out.println("服务器已启动...");
SocketChannel socketChannel = listener.accept();
//4读取数据
ByteBuffer buffer=ByteBuffer.allocate(1024);
socketChannel.read(buffer);
//5处理数据
buffer.flip();
String data=new String(buffer.array(),0,buffer.limit());
System.out.println(socketChannel.getRemoteAddress()+"说:"+data);
//6关闭
buffer.clear();
socketChannel.close();
listener.close();
}
}
客户端:
public class TcpClient {
public static void main(String[] args) throws Exception{
//1创建SocketChannel
SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("10.9.62.184", 8888));
//2写入
ByteBuffer buffer= ByteBuffer.allocate(1024);
buffer.put("好久不见".getBytes());
buffer.flip();
socketChannel.write(buffer);
//3关闭
buffer.clear();
socketChannel.close();
}
}
Selector
- Selector提供了询问通道是否已经准备好执行每个I/O操作的能力。
- Selector允许单线程处理多个Channel。
- 仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。这样会大量的减少线程之间上下文切换的开销。
- Selector:
- 选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。
- SelectionKey:
- 选择键封装了特定的通道与特定的选择器的注册关系。
- 选择键支持四种操作类型;
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
非阻塞式网络编程
服务端:
public class ChatServer {
public static void main(String[] args) throws Exception {
// 1 创建ServerSocketChannel
ServerSocketChannel listener = ServerSocketChannel.open();
// 2 绑定地址和端口号
listener.bind(new InetSocketAddress("10.9.62.184", 6666));
// 3设置模式为非阻塞模式
listener.configureBlocking(false);
// 4创建Selector 选择器(轮询器)
Selector selector = Selector.open();
// 5把listener注册到selector
listener.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("聊天室已启动...");
// 6轮询处理(阻塞方法) 返回键的个数
while (selector.select() > 0) {
// 7获取所有的选择键
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey selectionKey = it.next();
// 8如果有客户链接
if (selectionKey.isAcceptable()) {
// 9接收请求,不是阻塞方法
SocketChannel socketChannel = listener.accept();
// 10设置非阻塞模式
socketChannel.configureBlocking(false);
// 11注册selector
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 读取数据
// 12根据selectionKey 获取SocketChannel
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len;
try {
while ((len = channel.read(buffer)) > 0) {// 如果客户端没有正常关闭,出现异常
buffer.flip();
String data = new String(buffer.array(), 0, buffer.limit());
System.out.println(channel.getRemoteAddress() + "说:" + data);
buffer.clear();
}
// 正常结束
if (len == -1) {
System.out.println(channel.getRemoteAddress() + "退出了聊天");
channel.close();
}
} catch (IOException e) {
System.out.println(channel.getRemoteAddress() + "异常退出了聊天");
channel.close();
}
}
it.remove();// 处理过的键删除...
}
}
// 关闭listener
listener.close();
}
}
客户端:
public class ChatClient {
public static void main(String[] args) throws Exception {
// 1创建SocketChannel
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("10.9.62.184", 6666));
// 2设置为非阻塞模式
socketChannel.configureBlocking(false);
Scanner input = new Scanner(System.in);
// 3处理
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
String data = input.next();
buffer.put(data.getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
if (data.equals("886") || data.equals("byebye")) {
break;
}
}
// 4关闭
buffer.clear();
socketChannel.close();
}
}
[/wppay]