NIO笔记
一.IO是面向流,单向的;NIO是面向缓冲区
面向流是指在建立通道后,在通道上读取数据,直至读取到所有数据,数据没有被缓存到其他地方 ;
单向是指读数据要输入流,写数据要输出流;
缓存区先把数据存储在缓冲区,再通过通道把缓冲区的数据传输出去,而缓冲区既可以输入又可以输出;
1.1缓冲区(buffer)
在JAVA NIO中负责数据的存取;缓冲区相当于数组,不同类型有不同的缓冲区
(ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,LongBuffer,DoubleBuffer)
缓冲区的四大属性:
capacity:容量,表示缓冲区的最大容量
limit:界限,表示缓冲区中可以操作数据的大小(limit后面的数据不能进行读写)
position:位置,表示缓冲区中正在操作数据的位置
mark:标记,表示记录当前position的位置,可以通过调用reset()把position恢复到mark()
//获得一个指定大小的非直接缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
String str = "abcdegf";
System.out.println(buf.capacity());//1024
System.out.println(buf.position());//0
System.out.println(buf.limit());//1024
//向缓冲区写入数据
buf.put(str.getBytes());
System.out.println(buf.capacity());//1024
System.out.println(buf.position());//7
System.out.println(buf.limit());//1024
//切换从缓存区读取数据模式
buf.flip();
System.out.println(buf.capacity());//1024
System.out.println(buf.position());//0
System.out.println(buf.limit());//7
//从缓存区读取数据
byte [] bys = new byte[buf.limit()-1];
buf.get(bys);
System.out.println(new String(bys,0,bys.length));
//可重复读取缓冲区的数据
buf.rewind();
//清除缓冲区,数据依然存在,但处于被遗忘的状态,又可以重写写数据
buf.clear();
if(buf.hasRemaining()){//判断是否还有数据没有读完,以position是否到达limit为标准
System.out.println("---"+buf.remaining());//返回limit()-position()
}
//mark存储当前position的位置
buf.mark();
//把position恢复到mark的标记的位置
buf.reset();
直接缓冲区和非直接缓存区
非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM内存中
直接缓存区:通过allocateDirect()方法,将缓存区建立在OS物理内存中,少了OS缓冲区与jvm缓冲区之间的传输,提高了效率,但要等垃圾回收机制来释放缓冲区与程序的连接
//获取直接缓冲区
ByteBuffer buf_2 = ByteBuffer.allocateDirect(1024);
1.2通道(Channel)
DMA:通过向CPU发出请求来获取管理IO流的权力
通道(channel):全独立的处理器用于处理IO操作
通道通过非直接缓冲区传输文件
FileInputStream fis = new FileInputStream("文件1.txt");
FileOutputStream fos = new FileOutputStream("文件2.txt");
//获取通道
FileChannel inChannle = fis.getChannel();
FileChannel onChannle = fos.getChannel();
//获取指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
while(inChannle.read(buf)!=-1){//缓冲区读取文件
buf.flip();
onChannle.write(buf);//缓冲区写入文件
buf.clear();//清空缓冲区,重新读取文件
}
通过直接缓冲区传输文件的两种方式
内存映射文件
FileChannel inChannel = FileChannel.open(Paths.get("文件1.txt"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("文件2.txt"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
//内存映射文件
//把文件存放到缓冲区
MappedByteBuffer inMapperBuf = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
//读取缓冲区的文件
MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());
byte[] bt = new byte[inMapperBuf.limit()];
inMapperBuf.get(bt);
outMappedBuf.put(bt);
通道之间的数据传输
//inChannel.transferTo(0,inChannel.size(),onChannel);两者一样
outChannel.transferFrom(inChannel,0,inChannel.size());
分散与聚集
分散读取:将通道中的数据分散到多个缓冲区中
聚集写入:将多个缓冲区的数据聚集到通道中
二,IO是阻塞式,NIO是非阻塞式的
阻塞状态:一个线程调用read()或者write()时,线程会被堵塞,知道数据被读取完或写入完,前面的完成不了后面的也无法完成(用多线程可解决);
非阻塞状态:通过把通道注册到选择器上,选择器判断通道是否准备就绪,准备就绪后才会发到服务端的线程上,在被运行;客户端未准备就绪,服务端可以运行其他准备就绪任务;
非阻塞状态的Socket连接
public static void server() throws IOException {
//1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2.切换非堵塞模式
ssChannel.configureBlocking(false);
//3.绑定连接
ssChannel.bind(new InetSocketAddress(8888));
//4.获取选择器
Selector selector = Selector.open();
//5.将选择器注册到通道上,并且指定监听接收事件事件
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.轮询式的获取选择器上已经“准备就绪”的事件
while(selector.select()>0){//大于0说明有准备就绪的事件
//7.获取当前选择器中所有注册的“选择键(已就绪的监听状态)”
Iterator<SelectionKey> it = (Iterator<SelectionKey>) selector.selectedKeys().iterator();
while(it.hasNext()) {
//8.获取准备就绪的事件
SelectionKey sk = it.next();
//9.判断具体是什么事件准备就绪
if(sk.isAcceptable()){
//10.若“接收就绪”,获取客户端的连接
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();
}
}
//从集合中移除迭代器返回的最后一个元素
it.remove();
}
}
}
public static void client() throws IOException {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
//2.切换到非阻塞模式
sChannel.configureBlocking(false);
//3.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//4.发送数据给服务器
buf.put(new Date().toString().getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
//5.关闭通道
sChannel.close();
}