什么是NIO
- NIO是一个非阻塞的IO,面向于缓冲区的;缓冲区一般情况下只有一个但是可以有多个,将多个封装起来,看起来也是一个
- IO则是阻塞式的,面向于流的;
- NIO的效率要高于IO的效率
- 缓冲区是NIO提高给传输文件和通道一起配合使用,存储数据。读数据的时候,读的是缓冲区中的数据,写数据的时候也是写到缓冲区中。
- 缓冲区的种类:
(1)ByteBuffer
(2)LongBuffer
(3)IntegerBuffer
(4)FloatBuffer
(5)DoubleBuffer
(6)ShortBuffer
(7)CharBuffer
这里除了Boolean类型没有Buffer,其他每种基本类型都对应着一个Buffer。其中ByteBuffer用的是最多的。
Buffer
Buffer是NIO的缓冲区,Buffer有几个核心的参数。
- position
缓冲区正在操作的位置,默认从0开始 - limit
缓冲区可用大小,默认是capacity的位置,就是说明我们取的最大的下标其实就是limit-1的位置(因为下标是从0开始的) - capacity
缓冲区最大的容量,一旦声明,不能改变
还有几个Buffer的核心的方法
- allocate(int capacity )
表示声明的Buffer的最大长度 - clear()
只改变标记,不清楚数据
position = 0
limit = capacity
mark = -1 - flip()
limit = position
position = 0
mak = -1
每次读取前需要调用,表示开启读取模式 - rewind()
position = 0
mark = -1
该方法用于重复读取的时候用,读取前调用该方法可以重复读取 - put()
向buffer中存放数据 - get()
获取buffer中的数据
@Test
public void test001(){
// 初始化byteBuffer的大小
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
System.out.println("position: " + byteBuffer.position());
System.out.println("limit: " + byteBuffer.limit());
System.out.println("capacity: " + byteBuffer.capacity());
System.out.println("=============向ByteBuffer中赋值============");
byteBuffer.put("abcde".getBytes());
System.out.println("放值后的position: " + byteBuffer.position());
System.out.println("放值后的limit: " + byteBuffer.limit());
System.out.println("放值后的capacity: " + byteBuffer.capacity());
System.out.println("=============读取值================");
// 开启读取模式
byteBuffer.flip();
System.out.println("开启读取模式的position: " + byteBuffer.position());
System.out.println("开启读取模式的limit: " + byteBuffer.limit());
System.out.println("开启读取模式的capacity: " + byteBuffer.capacity());
byte[] bytes = new byte[byteBuffer.limit()];
// 从byteBuffer中读取数据,读到bytes中
byteBuffer.get(bytes);
System.out.println(new String(bytes, 0, bytes.length));
System.out.println("==============重复读取==============");
// 重复读取必须调用该方法将position归0
byteBuffer.rewind();
System.out.println("重复读取position: " + byteBuffer.position());
System.out.println("重复读取limit: " + byteBuffer.limit());
System.out.println("重复读取capacity: " + byteBuffer.capacity());
byte[] bytes2 = new byte[byteBuffer.limit()];
// 从byteBuffer中读取数据,读到bytes中
byteBuffer.get(bytes2);
System.out.println(new String(bytes2, 0, bytes2.length));
}
输出结果:
D:\java\jdk1.8\jdk\bin\java -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\idea\IntelliJ IDEA 2017.3.4\lib\idea_rt.jar=1431:D:\idea\IntelliJ IDEA 2017.3.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\idea\IntelliJ IDEA 2017.3.4\lib\idea_rt.jar;D:\idea\IntelliJ IDEA 2017.3.4\plugins\junit\lib\junit-rt.jar;D:\idea\IntelliJ IDEA 2017.3.4\plugins\junit\lib\junit5-rt.jar;D:\java\jdk1.8\jdk\jre\lib\charsets.jar;D:\java\jdk1.8\jdk\jre\lib\deploy.jar;D:\java\jdk1.8\jdk\jre\lib\ext\access-bridge-64.jar;D:\java\jdk1.8\jdk\jre\lib\ext\cldrdata.jar;D:\java\jdk1.8\jdk\jre\lib\ext\dnsns.jar;D:\java\jdk1.8\jdk\jre\lib\ext\jaccess.jar;D:\java\jdk1.8\jdk\jre\lib\ext\jfxrt.jar;D:\java\jdk1.8\jdk\jre\lib\ext\localedata.jar;D:\java\jdk1.8\jdk\jre\lib\ext\nashorn.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunec.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunjce_provider.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunmscapi.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunpkcs11.jar;D:\java\jdk1.8\jdk\jre\lib\ext\zipfs.jar;D:\java\jdk1.8\jdk\jre\lib\javaws.jar;D:\java\jdk1.8\jdk\jre\lib\jce.jar;D:\java\jdk1.8\jdk\jre\lib\jfr.jar;D:\java\jdk1.8\jdk\jre\lib\jfxswt.jar;D:\java\jdk1.8\jdk\jre\lib\jsse.jar;D:\java\jdk1.8\jdk\jre\lib\management-agent.jar;D:\java\jdk1.8\jdk\jre\lib\plugin.jar;D:\java\jdk1.8\jdk\jre\lib\resources.jar;D:\java\jdk1.8\jdk\jre\lib\rt.jar;D:\JavaProgram\test-nio\target\test-classes;D:\java\maven3.3.9\maven_respostory\junit\junit\4.12\junit-4.12.jar;D:\java\maven3.3.9\maven_respostory\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar" com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 com.xiyou.nio.test.TestNio,test001
position: 0
limit: 1024
capacity: 1024
=============向ByteBuffer中赋值============
放值后的position: 5
放值后的limit: 1024
放值后的capacity: 1024
=============读取值================
开启读取模式的position: 0
开启读取模式的limit: 5
开启读取模式的capacity: 1024
abcde
==============重复读取==============
重复读取position: 0
重复读取limit: 5
重复读取capacity: 1024
abcde
Process finished with exit code 0
make与reset的区别
标记(make)与重置(reset):标记是一个索引,通过Buffer中的mark()方法指定Buffer中一个特定的position,之后可以通过调用reset()方法恢复到这个position。
@Test
public void test002(){
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
byteBuffer.put("abcde".getBytes());
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.limit()];
// 设置一个标记,此时的position为0(因为调用了flip)
byteBuffer.mark();
// 从byteBuffer中取值,从0开始取长度为2
byteBuffer.get(bytes, 0, 2);
System.out.println("读值后的position: " + byteBuffer.position());
System.out.println("读值后的limit: " + byteBuffer.limit());
System.out.println("读值后的capacity: " + byteBuffer.capacity());
// reset,将position回归到本来的地方
byteBuffer.reset();
System.out.println("reset后的position: " + byteBuffer.position());
System.out.println("reset后的limit: " + byteBuffer.limit());
System.out.println("reset后的capacity: " + byteBuffer.capacity());
byteBuffer.clear();
}
结果显示:
D:\java\jdk1.8\jdk\bin\java -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\idea\IntelliJ IDEA 2017.3.4\lib\idea_rt.jar=4258:D:\idea\IntelliJ IDEA 2017.3.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\idea\IntelliJ IDEA 2017.3.4\lib\idea_rt.jar;D:\idea\IntelliJ IDEA 2017.3.4\plugins\junit\lib\junit-rt.jar;D:\idea\IntelliJ IDEA 2017.3.4\plugins\junit\lib\junit5-rt.jar;D:\java\jdk1.8\jdk\jre\lib\charsets.jar;D:\java\jdk1.8\jdk\jre\lib\deploy.jar;D:\java\jdk1.8\jdk\jre\lib\ext\access-bridge-64.jar;D:\java\jdk1.8\jdk\jre\lib\ext\cldrdata.jar;D:\java\jdk1.8\jdk\jre\lib\ext\dnsns.jar;D:\java\jdk1.8\jdk\jre\lib\ext\jaccess.jar;D:\java\jdk1.8\jdk\jre\lib\ext\jfxrt.jar;D:\java\jdk1.8\jdk\jre\lib\ext\localedata.jar;D:\java\jdk1.8\jdk\jre\lib\ext\nashorn.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunec.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunjce_provider.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunmscapi.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunpkcs11.jar;D:\java\jdk1.8\jdk\jre\lib\ext\zipfs.jar;D:\java\jdk1.8\jdk\jre\lib\javaws.jar;D:\java\jdk1.8\jdk\jre\lib\jce.jar;D:\java\jdk1.8\jdk\jre\lib\jfr.jar;D:\java\jdk1.8\jdk\jre\lib\jfxswt.jar;D:\java\jdk1.8\jdk\jre\lib\jsse.jar;D:\java\jdk1.8\jdk\jre\lib\management-agent.jar;D:\java\jdk1.8\jdk\jre\lib\plugin.jar;D:\java\jdk1.8\jdk\jre\lib\resources.jar;D:\java\jdk1.8\jdk\jre\lib\rt.jar;D:\JavaProgram\test-nio\target\test-classes;D:\java\maven3.3.9\maven_respostory\junit\junit\4.12\junit-4.12.jar;D:\java\maven3.3.9\maven_respostory\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar" com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 com.xiyou.nio.test.TestNio,test002
读值后的position: 2
读值后的limit: 5
读值后的capacity: 10
reset后的position: 0
reset后的limit: 5
reset后的capacity: 10
Process finished with exit code 0
直接缓冲区和非直接缓冲区
- 非直接缓冲区主要存放在JVM缓冲区中,需要来回拷贝,就是说JVM需要和物理内存互相拷贝数据
ByteBuffer.allocate()是非直接缓冲区 - 直接缓冲区是存放在物理内存中的,不需要来回拷贝
ByteBuffer.allocateDirect()是直接缓冲区 - 非直接缓冲区更加安全
- 直接缓冲区占用内存,直接向内存中写数据
- 我们通常用的都是非直接缓冲区
存放在物理内存要比存放在JVm中效率高,因此直接缓冲区的效率要高
Channel通道
通道表示打开到IO设备的连接。若需要使用NIO系统,则需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。Channel负责传输,Buffer负责存储,Channel本身不能直接访问数据,Channel只能和Buffer进行交互。可以把Channel理解为输入输出流,但是他只操作buffer
Channel有以下部分接口:
(1)FileChannel
(2)SocketChannel
(3)ServerSocketChannel
(4)DatagramChannel
非直接缓冲区
/**
* 非直接缓冲区的读写操作
*/
@Test
public void test003() throws Exception {
// 输入流
FileInputStream fis = new FileInputStream("D:\\csdn_img\\1.png");
// 输出流
FileOutputStream fos = new FileOutputStream("2.png");
// 创建通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
// 指定缓冲区的大小 非直接缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 从管道中向buffer中读取数据
while (inChannel.read(byteBuffer) != -1){
byteBuffer.flip();
// 从buffer中向管道中写数据
outChannel.write(byteBuffer);
byteBuffer.clear();
}
// 关闭通道,关闭连接
inChannel.close();
outChannel.close();
fis.close();
fos.close();
}
直接缓冲区
/**
* 直接缓冲区
* @throws Exception
*/
@Test
public void test004() throws Exception {
// 创建通道
FileChannel inChannel = FileChannel.open(Paths.get("D:\\csdn_img\\1.png"), StandardOpenOption.READ);
// 读写权限,如果文件不存在创建一个新文件
FileChannel outChannel = FileChannel.open(Paths.get("3.png"),
StandardOpenOption.READ,
StandardOpenOption.WRITE, StandardOpenOption.CREATE);
MappedByteBuffer inMapBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMapBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
// 直接对缓冲区操作
byte[] bytes = new byte[inMapBuffer.limit()];
inMapBuffer.get(bytes);
outMapBuffer.put(bytes);
inChannel.close();
outChannel.close();
}
上面是通过buffer的get和put声明了两个buffer互相传递,下面的写法更加常用,通俗易懂
@Test
public void test005() throws Exception {
// 输入流
FileInputStream fis = new FileInputStream("D:\\csdn_img\\1.png");
// 输出流
FileOutputStream fos = new FileOutputStream("4.png");
FileChannel fisChannel = fis.getChannel();
FileChannel fosChannel = fos.getChannel();
MappedByteBuffer buffer = fisChannel.map(FileChannel.MapMode.READ_ONLY, 0, fisChannel.size());
fosChannel.write(buffer);
fisChannel.close();
fosChannel.close();
}
分散读取聚集写入
- 分散读取
将通道中的数据分散到多个缓冲区中
- 聚集写入
将多个缓冲区的数据聚集到通道中
/**
* 分散读取和聚集写入
* @throws Exception
*/
@Test
public void test006() throws Exception{
RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");
// 获取通道
FileChannel rafChannel = raf.getChannel();
// 分配指定大小的缓冲区
ByteBuffer byteBuffer1 = ByteBuffer.allocate(100);
ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
// 分散读取,利用两个buffer进行读取和写入
ByteBuffer[] buffers = {byteBuffer1, byteBuffer2};
rafChannel.read(buffers);
for(ByteBuffer byteBuffer : buffers){
byteBuffer.flip();
}
System.out.println("第一个buffer:" + new String(buffers[0].array(), 0, buffers[0].limit()));
System.out.println("第二个buffer:" + new String(buffers[1].array(), 0, buffers[1].limit()));
// 聚集写入
RandomAccessFile randomAccessFile = new RandomAccessFile("2.txt", "rw");
FileChannel outChannel = randomAccessFile.getChannel();
outChannel.write(buffers);
}
其中输出的结果是:
D:\java\jdk1.8\jdk\bin\java -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\idea\IntelliJ IDEA 2017.3.4\lib\idea_rt.jar=3148:D:\idea\IntelliJ IDEA 2017.3.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\idea\IntelliJ IDEA 2017.3.4\lib\idea_rt.jar;D:\idea\IntelliJ IDEA 2017.3.4\plugins\junit\lib\junit-rt.jar;D:\idea\IntelliJ IDEA 2017.3.4\plugins\junit\lib\junit5-rt.jar;D:\java\jdk1.8\jdk\jre\lib\charsets.jar;D:\java\jdk1.8\jdk\jre\lib\deploy.jar;D:\java\jdk1.8\jdk\jre\lib\ext\access-bridge-64.jar;D:\java\jdk1.8\jdk\jre\lib\ext\cldrdata.jar;D:\java\jdk1.8\jdk\jre\lib\ext\dnsns.jar;D:\java\jdk1.8\jdk\jre\lib\ext\jaccess.jar;D:\java\jdk1.8\jdk\jre\lib\ext\jfxrt.jar;D:\java\jdk1.8\jdk\jre\lib\ext\localedata.jar;D:\java\jdk1.8\jdk\jre\lib\ext\nashorn.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunec.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunjce_provider.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunmscapi.jar;D:\java\jdk1.8\jdk\jre\lib\ext\sunpkcs11.jar;D:\java\jdk1.8\jdk\jre\lib\ext\zipfs.jar;D:\java\jdk1.8\jdk\jre\lib\javaws.jar;D:\java\jdk1.8\jdk\jre\lib\jce.jar;D:\java\jdk1.8\jdk\jre\lib\jfr.jar;D:\java\jdk1.8\jdk\jre\lib\jfxswt.jar;D:\java\jdk1.8\jdk\jre\lib\jsse.jar;D:\java\jdk1.8\jdk\jre\lib\management-agent.jar;D:\java\jdk1.8\jdk\jre\lib\plugin.jar;D:\java\jdk1.8\jdk\jre\lib\resources.jar;D:\java\jdk1.8\jdk\jre\lib\rt.jar;D:\JavaProgram\test-nio\target\test-classes;D:\java\maven3.3.9\maven_respostory\junit\junit\4.12\junit-4.12.jar;D:\java\maven3.3.9\maven_respostory\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar" com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 com.xiyou.nio.test.TestNio,test006
第一个buffer:1234567890asdfghjm,ajksldjaslkdijmwefsdjkasdhjkdhhjkl进口到付款计划的时刻福建省地方�
第一个buffer:�收到,浪费就死定了,福建省的来访接待室了,打飞机离开房间是考虑到福建省都快来减肥的克里斯就分手快乐的积分克鲁赛德反倒是龙卷风快乐圣诞节是否考虑到积分克鲁赛德风口浪尖风口浪尖房卡洛斯京东方卡兰蒂斯荆防颗粒圣诞节快乐方式健康法律纠纷克鲁赛德快乐番薯打开了房间的上课了的房间收款方见识到了福建省的考虑对方监考老师放假考虑到双方均代课老师积分快乐圣诞节福利卡三等奖付款了电视剧房卡洛斯积分代课老师积分立刻升级法律会计师对方立刻就死定了客服解释道六块腹肌圣诞快乐发角度看了发动机是开了房间单身快乐发送到了发送到离开方式打开了方式看到了房卡洛斯积分克鲁赛德积分可适当积分克鲁赛德就抗裂砂浆风口浪尖圣诞快乐发电机房考虑是否健康好囧未付款了维护积分克鲁赛德风口浪尖付款了无法快乐考虑快乐圣诞节快乐福
Process finished with exit code 0
从结果可以看到,我们将内容写到buffer中的时候,先写到buffer1中,当buffer1满了的时候就写到buffer2里面,从buffer中向出写也是同样。
字符集
NIO还为我们提供了编码器和解码器
- 编码:
字符串->字节数组 - 解码:
字节数组->字符串
可以这么理解下,编码就是将人能看懂的变成人看不懂的,解码就是将人看不懂的变成人能看懂的。
/**
* 测试编码和解码
* @throws Exception
*/
@Test
public void test007() throws Exception{
// 设定格式UTF-8
Charset charset = Charset.forName("UTF-8");
// 获取编码器
CharsetEncoder charsetEncoder = charset.newEncoder();
// 获取解码器
CharsetDecoder charsetDecoder = charset.newDecoder();
CharBuffer charBuffer = CharBuffer.allocate(1024);
charBuffer.put("西安邮电大学最棒");
charBuffer.flip();
// 对buffer编码
ByteBuffer encodeBuffer = charsetEncoder.encode(charBuffer);
byte[] bytes = new byte[encodeBuffer.limit()];
encodeBuffer.get(bytes);
System.out.println(bytes);
encodeBuffer.flip();
CharBuffer decodeBuffer = charsetDecoder.decode(encodeBuffer);
System.out.println(decodeBuffer.get());
}
总结一下:
我们在使用NIO的时候一定要注意limit position这两个下标的位置,这两个决定了你能否正常的读取。