打卡日期(2019-07-24)
学习要点
- 1.nio介绍
- 2.nio与传统io的区别
- 3.通道(Channel)和缓冲区(Buffer)
- 4.缓冲区-Buffer
- 5.缓冲区四个核心属性
- 6.直接缓冲区和非直接缓冲区
- 7.通道-Channel
- 8.通道的分类
- 9.通道之间的数据传输
- 10.通道分散(Scatter)与聚集(Gatter)
- 11.选择器-selector
1.nio介绍
java nio是一个new io,可以替代标准的Java IO API。与原来的IO有同样的作用和目的,但是适用方式完全不同,NIO支持面向缓冲区、基于通道的IO操作。NIO将以更加高效的方式进行文件读写操作。
2.nio与传统io的区别
IO | NIO |
---|---|
面向流(Stream) | 面向缓冲区(Buffer) |
阻塞IO(Block IO) | 非阻塞IO(Non Block IO) |
无 | 选择器(Selectors) |
3.通道(Channel)和缓冲区(Buffer)
java nio的核心就在于通道和缓冲区
channel可以看做铁路,负责传输,
buffer可以看做火车,负责数据存取。
若需要使用nio,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
4.缓冲区-Buffer
缓冲区就是数组,用于存取不同的类型的数据,根据数据类型不同(boolean 除外)提供了相应类型的缓冲区
例如:(
ByteBuffer,
CharBuffer,
IntBuffer,
DouleBuffer,
ShortBuffer,
LongBuffer,
FloatBuffer),上述缓冲区的管理方式都是通过allocate()获取缓冲区
5.缓冲区四个核心属性
- capacity:容量,表示缓冲区中最大存储数据的容量,一旦声明大小不能改变。
- limit:界限,表示缓冲区中可以操作的数据大小。(limit 之后的数据不能操作)
- postion:位置,表示缓冲区正在操作数据的位置。
- mark:标记,表示记录当前postion的位置。可以通过reset()恢复到mark的位置。
0 <= mark <= postion <= limit <= capacity
//演示capacity,limit,postion之间的关联关系
@Test
public void testBuffer(){
String str = "abcdef";
//初始化容器
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("------------allocate()----------------");
System.out.println("capacity:"+buf.capacity()); //1024
System.out.println("limit:"+buf.limit()); //1024
System.out.println("position:"+buf.position()); //0
//往buf中put值
buf.put(str.getBytes());
System.out.println("------------put()----------------");
System.out.println("capacity:"+buf.capacity()); //1024
System.out.println("limit:"+buf.limit()); //1024
System.out.println("position:"+buf.position()); //6
//put完值之后切换读模式flip
buf.flip();
//1.此时limit的位置就是当前postion的位置
//2.postion的位置重置为0
System.out.println("------------flip()----------------");
System.out.println("capacity:"+buf.capacity()); //1024
System.out.println("limit:"+buf.limit()); //6
System.out.println("position:"+buf.position()); //0
System.out.println("------------get()----------------");
//get()从缓冲区中读取数据
byte[] dst = new byte[buf.limit()];
buf.get(dst);
System.out.println("capacity:"+buf.capacity()); //1024
System.out.println("limit:"+buf.limit()); //6
System.out.println("position:"+buf.position()); //6
System.out.println(new String(dst,0,dst.length));
//rewind之后可以重新进行读取数据,可重复读取数据
buf.rewind();
System.out.println("------------rewind()----------------");
System.out.println("capacity:"+buf.capacity()); //1024
System.out.println("limit:"+buf.limit()); //6
System.out.println("position:"+buf.position()); //0
//clear清空缓冲区,但是里面的数据并没有被清空,只是capacity,limit,postion恢复到初始化位置。
//clear之后,缓冲区的数据处于"被遗忘"状态
buf.clear();
//clear之后,原先的数据还在,只不过position为0,当你重新插入数据的时候,会把原先的数据给覆盖掉
System.out.println("------------clear()----------------");
System.out.println("capacity:"+buf.capacity()); //1024
System.out.println("limit:"+buf.limit()); //1024
System.out.println("position:"+buf.position()); //0
System.out.println((char)buf.get());//a
//mark作用,记录当前postion的位置,当调用reset()方法之后,恢复到记录postion的位置
@Test
public void testMark(){
String str = "abcdef";
//初始化容器
ByteBuffer buf = ByteBuffer.allocate(1024);
//往buf中put值
buf.put(str.getBytes());
//put完值之后切换读模式flip
buf.flip();
byte[] dst = new byte[buf.limit()];
//从缓冲区中读取两个字节,从0开始取2个
buf.get(dst,0,2);
System.out.println(new String(dst,0,2));//ab
System.out.println("position:"+buf.position());//2
//对当前的postion进行标记
buf.mark();//此时的postion位置2
//从缓冲区中再读取两个,从第2位开始读取2个
buf.get(dst,2,2);
System.out.println(new String(dst,2,2));//cd
//此时的postion的位置是4
System.out.println("position:"+buf.position());//4
//恢复到mark标记的位置
buf.reset();
System.out.println("position:"+buf.position());//2
}
6.直接缓冲区和非直接缓冲区
- 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM内存当中。
- 直接缓冲区:通过allocateDirect()方法分配缓冲区,将缓冲区建立在物理内存当中,可以适当提高效率。
7.通道-Channel
Channel表示io源与目标打开的链接,Channel类似于传统的“流”。只不过Channel本身不能直接访问数据,Channel只能与缓冲区(Buffer)进行交互
8.通道的分类
- FileChannel:从文件中读写数据
- SocketChannel:通过TCP读写网络中的数据
- ServerSocketChannel:可以监听新捡来的TCP链接,像Web服务器那样,对每一个新进来的链接都会创建一个SocketChannel
- DatagramChannel:能从UDP中读写网络中的数据
- 针对通道类型提供了getChannel()方法
- jdk1.7中的nio.2针对各个通道提供了静态方法open()
- jdk1.7中的nio.2的Files工具类的newByteChannel()
//方式1-getChannel()利用通道完成文件复制
@Test
public void testGetChannel() throws Exception{
FileInputStream fis = new FileInputStream("1.png");
FileOutputStream fos = new FileOutputStream("2.png");
//获取通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
//指定缓存区大小
ByteBuffer buf = ByteBuffer.allocate(1024);
//将通道中的数据写入缓存
while(inChannel.read(buf) != -1){
//切换读写模式
buf.flip();
//将缓存中的数据写入通道
outChannel.write(buf);
//清空缓存区
buf.clear();
}
outChannel.close();
inChannel.close();
fos.close();
fis.close();
}
//方式2-open()利用通道完成文件复制
//使用直接缓冲区完成文件复制(内存映射文件)
@Test
public void testOpen() throws Exception{
//1.获取通道
FileChannel inChannel = FileChannel.open(Paths.get("1.png"),StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
//内存映射文件
MappedByteBuffer inBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outBuf = outChannel.map(MapMode.READ_WRITE,0, inChannel.size());
//直接对缓冲区数据进行读写操作
byte[] dst = new byte[inBuf.limit()];
inBuf.get(dst);
outBuf.put(dst);
//关闭通道
inChannel.close();
outChannel.close();
}
9.通道之间的数据传输
将数据从一个通道复制到另一个通道
- transferFrom()
- transferTo()
//方式3-testtransfer()利用通道完成文件复制
//通道之间数据传输(直接缓冲区)
@Test
public void testNewByteChannel() throws Exception{
//1.获取通道
FileChannel inChannel = FileChannel.open(Paths.get("1.png"),StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
//transferTo 通道文件复制,从0开始:通道大小:输出通道
//transferFrom 通道文件copy,输入通道:0开始:通道大小
inChannel.transferTo(0, inChannel.size(), outChannel);
//outChannel.transferFrom(inChannel, 0, inChannel.size());
inChannel.close();
outChannel.close();
}
10.分散(Scatter)与聚集(Gatter)
- 分散读取(Scatter read):将通道的数据分散到多个缓冲区中
- 聚集写入(Gatter write):将多个缓冲区中的数据聚集写入到通道中
@Test
public void testSGatter() throws Exception{
RandomAccessFile raf1 = new RandomAccessFile("1.png","rw");
//获取通道
FileChannel inchannel = raf1.getChannel();
//指定缓冲区大小
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
//分散到缓冲区 Scatter
ByteBuffer[] bufs = {buf1,buf2};
inchannel.read(bufs);
for(ByteBuffer bb : bufs){
bb.flip();
//分别获取缓冲中的数据
System.out.println(new String(bb.array(),0,bb.limit())) ;
}
RandomAccessFile raf2 = new RandomAccessFile("2.png", "rw");
FileChannel outChannel = raf2.getChannel();
//聚集写入通道 Gatter
outChannel.write(bufs);
inchannel.close();
outChannel.close();
}
11.选择器-selector
Selector称为选择器,当然也可以翻译为多路复用。是java nio核心组件中的一个,用于检查一个或者多个通道(Channel)的状态是否处于可读,可写状态。
Selector管理的通道必须是非阻塞的,因为FileChannel为阻塞channel(没有继承SelectableChannel),所以文件通道不能被管理。
//通道设置非阻塞
channel.configureBlocking(false);
//客户端非阻塞发送消息
@Test
public void client() throws IOException{
// 1. 获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
// 2. 设置通道非阻塞
socketChannel.configureBlocking(false);
// 3. 声明缓存大小
ByteBuffer buf = ByteBuffer.allocate(1024);
// 4. 缓冲区存放数据
LocalDateTime date = LocalDateTime.now();
buf.put(("java nio 获取当前时间"+date.toString()).getBytes());
// 5. 往通道中写入数据
while(socketChannel.read(buf) != -1){
// 切换读状态
buf.flip();
socketChannel.write(buf);
buf.clear();
}
socketChannel.close();
}
//服务端非阻塞接收消息
@Test
public void server() throws IOException {
// 1.获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
// 2.设置非阻塞通道
ssChannel.configureBlocking(false);
//通道绑定端口号
ssChannel.bind(new InetSocketAddress(8888));
// 3.创建选择器Selector
Selector selector = Selector.open();
// 4.将通道注册到选择器当中,并且指定”监听接收事件“
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
// 5.轮训获取所有已经"准备好"的事件
while(selector.select() > 0){
// 6.获取当前选择器中所有注册的"已经就绪的监听事件"
Iterator<SelectionKey> sks = selector.selectedKeys().iterator();
while(sks.hasNext()){
// 7.获取准备就绪的状态
SelectionKey sk = sks.next();
// 8.判断是什么事件准备就绪
if(sk.isAcceptable()){
// 9.若接收就绪,就获取客户端的链接
SocketChannel sc = ssChannel.accept();
// 10.客户端通道设置非阻塞
sc.configureBlocking(false);
// 11.将改通道注册到选择器上
sc.register(selector, SelectionKey.OP_READ);
}else if(sk.isReadable()){
//System.out.println("开始读取数据...");
// 12.获取当期选择器的读就绪状态的通道
SocketChannel sc = (SocketChannel) sk.channel();
// 13.读取数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
while((len = sc.read(buf)) > 0){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
// 14.取消选择键
sks.remove();
}
}
}