简介
可以理解为Non Blocking Io
NIO和IO的区别
IO
之前的IO直接面向流,输入流和输出流需要单独建立
NIO
面向缓冲区,只需要一个通道(负责连接),输入和输出复用,缓冲区负责存储
通道和缓冲区
1.缓冲区
缓冲区(Buffer):在javaIO中负责数据的存储,缓冲区就是数组,用于储存不同的数据类型的数据
根据不同的数据类型(Boolean除外),提供了相应类型的缓冲区
ByteBuffer
CharBuffer
ShortBuffer
IntBUffer
LongBuffer
FloatBUffer
DoubleBuffer
上述缓冲区的管理方式是几乎一样的,通过allocate()获取缓冲区
最常用是ByteBuffer
二,缓冲区存取数据的两个核心办法:
put():存入数据到缓冲区中Buffer
get():取出缓冲区的数据BUffer
2. 缓冲区中的四个核心属性
> Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
补充:0<=mark<=position<=limit<=capacity
举例
String str="abcde";
//1.分配一个指定的1024字节的缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024);
System.out.println("----------------------- allocate---------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//2.利用put(str.getByts) 将数据村到缓冲区中
buf.put(str.getBytes());
System.out.println("----------------------- Put---------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//3.切换读取模式
buf.flip();
System.out.println("------------flip()--------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//4.利用get()读取缓冲区的数据
byte[] dst=new byte[buf.limit()];
buf.get(dst);
System.out.println(new String(dst,0,dst.length));
System.out.println("--------get---------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//5.rewind() 可重复读
buf.rewind();
System.out.println("--------rewind---------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//6.clear() 清空缓存区,这里的清空不是真的清空,只是处于“被遗忘”的状态
buf.clear();
System.out.println("--------clearn()---------");//只是指针初始化了
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
System.out.println((char)buf.get());
mack举例
//------------------mask 举例------------------------
ByteBuffer buf_mask=ByteBuffer.allocate(1024);
buf_mask.put(str.getBytes());
buf_mask.flip();
byte[] dst_mask=new byte[buf.limit()];
buf_mask.get(dst_mask,0,2);
System.out.println(new String(dst_mask,0,2));
System.out.println(buf_mask.position());
//mask:标记
buf_mask.mark();
buf_mask.get(dst_mask,2,2);
System.out.println(new String(dst_mask,2,2));
System.out.println(buf_mask.position());
//reset():恢复到mark的位置
buf_mask.reset();
System.out.println(buf_mask.position());
补充:0<=mark<=position<=limit<=capacity
直接缓冲区和非直接缓冲区:
非直接缓冲区
allocate()建立的是非直接缓冲区
copy耗费资源
直接缓冲区
弊端:
1.单独开辟物理内存,资源消耗大
2.写入到物理内存中的数据不归应用程序管,直接由操作系统负责,什么时候写入到物理储存就不可控制了
效率虽然高,不过数据不可控,资源消耗大,可以在某种特殊的环境下使用,比如数据长期放在内存中
为了安全,应用程序无法直接读写物理磁盘
举例:
2.文件通道
通道演进
DMA直接存储器存储,数据量大的话,DMA总线过多,会造成总线冲突,
通道:一个完全独立的处理器,专门用于IO操作,附属于cpu,能满足大量的IO操作
fileChannel用于本地,其余用于网络
获取通道的三种方式
使用通道非直接缓存区进行读取操作
使用直接缓存区进行文件进行读取操作
通道之间的数据传输(直接缓存区)
通道之间的数据传输(直接缓冲区)这种性能最高
3.分散(Scattering)与聚集(Gather)
分散读取(Scattering Reads):Jiang
package com.tanghc.java;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class Test4{
public static void main(String args[]) throws IOException {
RandomAccessFile raf1=new RandomAccessFile("1.txt","rw");
// 获取通道
FileChannel channel1=raf1.getChannel();
// 分配制定大小的多个缓存区
ByteBuffer buf1=ByteBuffer.allocate(100);
ByteBuffer buf2=ByteBuffer.allocate(1024);
// 分散读取
ByteBuffer[] bufs={buf1,buf2};
channel1.read(bufs);
for ( ByteBuffer byteBuffer:bufs){
byteBuffer.flip();
}
System.out.println(new String(bufs[0].array(),0,bufs[0].limit()));
System.out.println("----------------------------");
System.out.println(new String(bufs[1].array(),0,bufs[1].limit()));
// 聚集写入
RandomAccessFile raf2=new RandomAccessFile("2.txt","rw");
FileChannel channel2=raf2.getChannel();
channel2.write(bufs);
//关闭所有
raf1.close();
raf2.close();
channel1.close();
channel2.close();
}
}
字符集:Charset
编码:字符串-》字节数组
解码: 字节数组-》字符串
package com.tanghc.java;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
public class TestCharset {
public static void main(String[] args) throws IOException {
Charset cs1=Charset.forName("GBK");
// 获取解码器
CharsetDecoder cd=cs1.newDecoder();
// 获取编码器
CharsetEncoder ce=cs1.newEncoder();
CharBuffer cBuf=CharBuffer.allocate(1024);
cBuf.put("字符集解码编码");
cBuf.flip();
// 编码
ByteBuffer bBuf=ce.encode(cBuf);
for(int i=0;i<12;i++){
System.out.println(bBuf.get());
}
// 解码
bBuf.flip();
CharBuffer cBuf2=cd.decode(bBuf);
System.out.println(cBuf2.toString());
System.out.println("------------------");
Charset cs2=Charset.forName("UTF-8");
bBuf.flip();
CharBuffer cBuf3=cs2.decode(bBuf);
System.out.println(cBuf3.toString());
}
}
到这里通道的本地文件就讲完了,接下来是网络通信(核心)
NIO的非阻塞式网络通信,IO是阻塞式的
IO阻塞式:
一个线程,一个连接完成才继续下一个连接
NIO:多线程解决IO阻塞问题:(还是会阻塞)
NIO的非阻塞模式:
加一层:Selector选择器(操作系统的能力)
选择器监控每个通道,当每个通道完全准备就绪时,才会将任务分配到服务端上一个或者多个的线程上运行
NIO 网络通信:
selector用来监视channel
FILEchannel不能切换成非阻塞模式
NIO阻塞式编程:
网络通信,阻塞式IO
客户端client:
package com.tanghc.java;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.Pipe;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class TestblockingNio_Client {
public static void main(String args[]) throws IOException{
// 获取socketChannal通道
SocketChannel sChannel=SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));
FileChannel inChannel=FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
// 2.分配指定大小的缓存区
ByteBuffer buf=ByteBuffer.allocate(1024);
// 读取本地文件,并发送客户端
while (inChannel.read(buf) != -1){
buf.flip();
sChannel.write(buf);
buf.clear();
}
// 关闭通道
inChannel.close();
sChannel.close();
}
}
package com.tanghc.java;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class TestblockingNio_Server {
public static void main(String[] args) throws IOException {
// 获取通道
ServerSocketChannel ssChannel
= ServerSocketChannel.open();
FileChannel outchanel = FileChannel.
open(Paths.get("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.READ);
// 绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//3.获取客户端连接的通道
SocketChannel sChannel=ssChannel.accept();
//分配制定大小的缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024);
// 5.接受客户端的数据,并保存到本地
while(sChannel.read(buf) != -1){
buf.flip();
outchanel.write(buf);
buf.clear();
}
//6.关闭通道
sChannel.close();
outchanel.close();
ssChannel.close();
}
}
先启动服务端再启动客户端
服务端给客户端反馈案例:
非阻塞式IO
此时线程处于阻塞状态
解决方法有两种:
1.
2,把线程改为非阻塞模式
非阻塞式编程
package com.tanghc.java;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Scanner;
public class TestblockingNio_Client_fzs {
public static void main(String[] args) throws IOException {
SocketChannel sChannel=SocketChannel.open(new InetSocketAddress("localhost",9898));
//2.切换非阻塞模式
sChannel.configureBlocking(false);
// 分配指定大小的缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024);
// 发送数据到服务端
Scanner scan=new Scanner(System.in);
while(scan.hasNext()){
String str=scan.next();
buf.put((new Date().toString()+'\n'+str).getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
}
// 关闭通道
sChannel.close();
}
}
package com.tanghc.java;
import javax.sound.midi.Soundbank;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.sql.SQLOutput;
import java.util.Iterator;
public class TestblockingNio_Server_fzs {
public static void main(String[] args) throws IOException {
// 获取通道
ServerSocketChannel ssChannel=ServerSocketChannel.open();
// 切换为非阻塞模式
ssChannel.configureBlocking(false);
// 3.绑定连接
ssChannel.bind(new InetSocketAddress(9898));
// 4.获取选择器
Selector selector=Selector.open();
// 5 将通道注册到选择器上,并且指定“监听事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
// 6轮训式的获取选择器上已经准备就绪的事件
while(selector.select()>0){
// 7 获取当前选择器中所有注册的“选择键(已就绪的监听事件)" Iterable 集合,可迭代
Iterator<SelectionKey> it=selector.selectedKeys().iterator();
while(it.hasNext()){
// 8 获取准备就绪的事件
SelectionKey sk=it.next();
// 判断具体是什么事件准备就绪
if(sk.isAcceptable()){
SocketChannel sChannel=ssChannel.accept();
// 11.切换非阻塞模式
sChannel.configureBlocking(false);
// 12.将该通道注册到选择器上
sChannel.register(selector,SelectionKey.OP_READ);
}else if(sk.isReadable()){
// 13.获取当前选择器上“读就绪”状态的通道
SocketChannel sChannel= (SocketChannel) sk.channel();
// 14.读取数据
ByteBuffer buf=ByteBuffer.allocate(1024);
int len=0;
while((len=sChannel.read(buf))>0){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
// 15.取消选择键SelectionKey
it.remove();
}
}
}
}
注册通道选择键ops:
DdatagramChannel
UDP区别于TCP,无需连接就发送消息,不保证能接收到
pipe管道
package com.tanghc.java;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
public class TestPipe {
public static void main(String[] args) throws IOException {
Pipe pipe=Pipe.open();
//2.将缓冲区的数据写入到 管道
ByteBuffer buf=ByteBuffer.allocate(1024);
Pipe.SinkChannel sinkChannel=pipe.sink();
buf.put("通过像管道发送数据".getBytes());
buf.flip();
sinkChannel.write(buf);
// 3.读取缓冲区的数据
Pipe.SourceChannel sourceChannel=pipe.source();
buf.flip();
int len=sourceChannel.read(buf);//返回值为读取的缓冲区的大小
System.out.println(new String(buf.array(),0,len));
sourceChannel.close();
sinkChannel.close();
}
}
github和gitEE?