NIO
Java Non-blocking IO或Java New IO,是从JDK1.4 开始引入 的一套新的IO,为所有的原始类型(boolean类型除外)提供缓存支持的 数据容器
使用它可以提供非阻塞式的高伸缩性网络
jdk 1.7后加入AIO(NIO2)
BIO:阻塞IO NIO:非阻塞IO AIO;异步IO
Buffer缓冲区
子类中没有boolean
Buffer基本使用
allocate()堆中开辟 alloacteDirect()物理内存开辟
用于接收的字符数组长度不能大于缓冲区长
public class TestBuffer {
public static void main(String[] args) {
// 1.创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);// 实际上就是分配一个数组
// 2.put方法存放珊瑚橘
byte[] bytes = "chichi yyds".getBytes();
buffer.put(bytes);
// 3.翻转缓冲区(把写入模式转为读读取模式)
buffer.flip();
// 4.读取 字符数组长度不能大于缓冲区长
byte[] bytes1 = new byte[buffer.limit()];
buffer.get(bytes1);
System.out.println(new String(bytes1,0,bytes.length));
// 5.清空
buffer.clear();
}
}
Buffer原理
mark<=position<=limit<=capcity 标记 位置, 限制, 容量
Buffer使用过程
创建后默认为写模式,position初始数组0下标(初始位置),limit与capcity都在末尾,每次写入position后移,flip()后,limit指向此时position的位置,position回到起始位置。变为读模式
清空clear后,三个指针回到缓冲区创建的初始位置(clear并没有删除数据,数据处于遗忘状态,再次存入数据时会覆盖原数据),但是已经读不到数据了(limit限制,只能到达新元素的位置),
使用compact方法可以保存未读数据(此时position~limit之间剩余)到Buffer起始处,position指向最后一个未读元素后边,(未读数据存在)flip()翻转后可以再次读到数据了
mark标记可以理解为一个书签,调用mark()方法可以在此时position打上一个标签,当position后移后,调用reset方法可以把position置为mark位置
Channel通道
后buffer为模式才能 read方法(从通道中读取,写入到缓冲区中)
阻塞模式通道的read为阻塞方法,读完自己想要的才会通过
buffer为读模式才能 write(从缓冲区中读出写到通道中)
直接缓冲区不能使用.array()方法返回数组
创建通道的四种方式
1.使用字节流或RandomAccessFile来获取一个FileChannel实例
2.JDK1.7之后才能使用, FileChannel.open()方法
3.使用工具类Channels的newChannel()方法
4.文件字节流
// 1 使用字节文件流
RandomAccessFile file = new RandomAccessFile("chichi.text", "rw");
FileChannel channel = file.getChannel();
// 2.使用工具类
FileChannel Channel = (FileChannel)Channels.newChannel(new FileOutputStream("chichi.txt"))
// 3.open方法 打开方式若为create_new,文件错在会报错
FileChannel open = FileChannel.open(Paths.get("chichi.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
// 4.文件字节流
FileOutputStream fileOutputStream= new FileOutputStream("d:\\hah.txt");
FileChannel channel=fileOutputStream.getChannel();
channel通道写
public static void write() throws Exception{
// 1.2open方法 打开方式若为create_new,文件错在会报错
FileChannel open = FileChannel.open(Paths.get("chichi.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
ByteBuffer allocate = ByteBuffer.allocate(1024);
allocate.put("赤赤 永远的神".getBytes());
//转换模式 写入通道
allocate.flip();
open.write(allocate);
// 关闭 channel关闭会自动关闭缓冲区
open.close();
}
channel关闭会自动关闭缓冲区
channel通道读
public static void read() throws Exception{
//工具类创建通道
// FileChannel Channel = (FileChannel)Channels.newChannel(new FileInputStream("chichi.txt"));
FileChannel open = FileChannel.open(Paths.get("chichi.txt"), StandardOpenOption.READ);
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// 创建解码器 当接收缓冲区空间小,读取汉字时可能会因读不全(三个字节)而产生乱码
// 英文时无此问题,直接用ByteBuffer读就可以
// 通过解码器,把ByteBuffer解码成 CharBuffer
CharsetDecoder charsetDecoder = Charset.forName("utf-8").newDecoder();
CharBuffer charBuffer = CharBuffer.allocate(10);//大小要有
//read时把通道中的读到Buffer中,时写模式
while (open.read(byteBuffer)>0){//读到末尾会返回0,-1,
byteBuffer.flip();//变写为读
charsetDecoder.decode(byteBuffer, charBuffer, false);//false默认
charBuffer.flip();//变写为读
System.out.println(charBuffer.toString());
byteBuffer.compact();//可能有没读完的字节,不能clear
charBuffer.clear();
}
open.close();
}
普通IO操作
文件大的时候使用直接内存,文件小的时候用间接内存
直接缓冲区的使用,可以提高读写的速度。但是直接缓冲区的创建和销毁的 开销比较大,一般大文件操作或能显著提高读写性能时使用
内存映射
• 内存映射文件也属于直接缓冲区
注意:如果文件超过2G,需要分多个文件映射
FileChannel readChannel = FileChannel.open(Paths.get("kekeluo.jpg"), StandardOpenOption.READ);
FileChannel writeChannel = FileChannel.open(Paths.get("chichi.jpg"), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
// 直接内存映射文件 文件超过2G,采用多映射
MappedByteBuffer map=readChannel.map(FileChannel.MapMode.READ_ONLY,0,1);
MappedByteBuffer map2=readChannel.map(FileChannel.MapMode.READ_ONLY,1, readChannel.size()-1);
// 内存映射文件写入
writeChannel.write(map);
writeChannel.write(map2);
// 关闭
writeChannel.close();
readChannel.close();
System.out.println("复制完毕");
NIO网络编程(TCP)
应对任务小,高并发
既支持阻塞式又支持非阻塞式
NIO阻塞式编程
public class Server {
public static void main(String[] args) throws Exception{
ServerSocketChannel listener=ServerSocketChannel.open();
// 绑定地址和端口号
listener.bind(new InetSocketAddress("10.0.139.195",8888));
// 监听
SocketChannel socketChannel = listener.accept();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
socketChannel.read(byteBuffer);
byteBuffer.flip();
System.out.println(socketChannel.getRemoteAddress()+new String(byteBuffer.array(),0,byteBuffer.limit()));//array返回数组
byteBuffer.clear();
//关闭
socketChannel.close();
listener.close();
}
}
public static void main(String[] args) throws Exception{
// 客户端可以直接地址(客户端的)
SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("10.0.139.195",8888));
ByteBuffer buffer=ByteBuffer.allocate(1024);
buffer.put("chichi yyds".getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
socketChannel.close();
}
Selector
多路复用
NIO非阻塞式网络编程
public class Server {
public static void main(String[] args) throws Exception{
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
// 绑定地址和端口
serverSocketChannel.bind(new InetSocketAddress("10.0.139.195", 8888));
// 设置为非阻塞式
serverSocketChannel.configureBlocking(false);
// 创建选择器
Selector selector=Selector.open();
// 注册到选择器
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 轮询 select() 阻塞方法,没有时间发送阻塞(选择器中没有位置)
// 返回已更新其准备就绪操作集的键的数目,该数目可能为零
System.out.println("客户端启动了");
while (selector.select()>0){
//返回可用的键集
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
if(selectionKey.isAcceptable()){
//表示有客户端请求
SocketChannel socketChannel = serverSocketChannel.accept();//不会阻塞,可能有其他的在运行
System.out.println(socketChannel.getRemoteAddress()+"进入聊天室");
//设置非阻塞
socketChannel.configureBlocking(false);
//注册到selector
socketChannel.register(selector,SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){
//获取到此键所对应的通道
SocketChannel channel = (SocketChannel)selectionKey.channel();
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
int len=0;
//客户端发送空,len为0
//如果某个通道处于阻塞模式,并且缓冲区中至少剩余一个字节,则在读取至少一个字节之前将阻塞此方法
try {
while ((len=channel.read(byteBuffer))>0){//不会阻塞
byteBuffer.flip();
System.out.println(channel.getRemoteAddress()+":::"+new String(byteBuffer.array(),0,byteBuffer.limit()));
byteBuffer.clear();
}
if (len==-1){
System.out.println(channel.getRemoteAddress()+"退出了聊天室");
channel.close();
}
} catch (IOException e) {
System.out.println(channel.getRemoteAddress()+"异常退出了");
channel.close();
}
}
//处理过的键删除
iterator.remove();
}
}
serverSocketChannel.close();
}
}
public class Client {
public static void main(String[] args) throws Exception{
Scanner scanner = new Scanner(System.in);
SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("10.0.139.195", 8888));
socketChannel.configureBlocking(false);
while (true){
String data=scanner.nextLine();
if("886".equals(data)){
break;
}
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
byteBuffer.put(data.getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
socketChannel.close();
}
}
注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。
通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。
一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道
当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。
注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。