Java NIO使用及原理分析
2016-01-11 20:17
543 查看
转载自:http://blog.csdn.net/wuxianglong/article/details/6604817
在NIO中有几个核心对象需要掌握:缓冲区(Buffer)、通道(Channel)、选择器(Selector)。
在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer,对于Java中的基本类型,基本都有一个具体Buffer类型与之相对应,它们之间的继承关系如下图所示:
Buffer对象最重要的属性有下面三个,它们一起合作完成对缓冲区内部状态的变化跟踪:
position:指定了下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer对象时,position被初始化为0。
limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。
以上三个属性值之间有一些相对大小的关系:0 <= position <= limit <= capacity。如果创建一个新的容量大小为10的ByteBuffer对象,在初始化的时候,position设置为0,limit和 capacity被设置为10,在以后使用ByteBuffer对象过程中,capacity的值不会再发生变化,而其它两个将会随着使用而变化。
如果向缓冲区中写入4个数据后,则此时position的值为4,即下一个将要被写入的字节索引为4,而limit仍然是10,如下图所示:
下一步把读取的数据写入到输出通道中,相当于从缓冲区中读取数据,在此之前,必须调用flip()方法,该方法将会完成两件事情:
1. 把limit设置为当前的position值
2. 把position设置为0
由于position被设置为0,所以可以保证在下一步输出时读取到的是缓冲区中的第一个字节,而limit被设置为当前的position,可以保证读取的数据正好是之前写入到缓冲区中的数据,如下图所示:
现在调用get()方法从缓冲区中读取数据写入到输出通道,这会导致position的增加而limit保持不变,但position不会超过limit的值,所以在读取我们之前写入到缓冲区中的4个自己之后,position和limit的值都为4,如下图所示:
在从缓冲区中读取数据完毕后,limit的值仍然保持在我们调用flip()方法时的值,调用clear()方法能够把所有的状态变化设置为初始化时的值,如下图所示:
简单示例:
在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口。它们之间的继承关系如下图所示:
使用NIO读取数据
在前面我们说过,任何时候读取数据,都不是直接从通道读取,而是从通道读取到缓冲区。所以使用NIO读取数据可以分为下面三个步骤:
1. 从FileInputStream获取Channel
2. 创建Buffer
3. 将数据从Channel读取到Buffer中
下面是一个简单的使用NIO从文件中读取数据的例子:
使用NIO写入数据与读取数据的过程类似,同样数据不是直接写入通道,而是写入缓冲区,可以分为下面三个步骤:
1. 从FileInputStream获取Channel
2. 创建Buffer
3. 将数据从Channel写入到Buffer中
下面是一个简单的使用NIO向文件中写入数据的例子:
在NIO中有几个核心对象需要掌握:缓冲区(Buffer)、通道(Channel)、选择器(Selector)。
缓冲区Buffer
缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer,对于Java中的基本类型,基本都有一个具体Buffer类型与之相对应,它们之间的继承关系如下图所示:
Buffer对象最重要的属性有下面三个,它们一起合作完成对缓冲区内部状态的变化跟踪:
position:指定了下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer对象时,position被初始化为0。
limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。
以上三个属性值之间有一些相对大小的关系:0 <= position <= limit <= capacity。如果创建一个新的容量大小为10的ByteBuffer对象,在初始化的时候,position设置为0,limit和 capacity被设置为10,在以后使用ByteBuffer对象过程中,capacity的值不会再发生变化,而其它两个将会随着使用而变化。
如果向缓冲区中写入4个数据后,则此时position的值为4,即下一个将要被写入的字节索引为4,而limit仍然是10,如下图所示:
下一步把读取的数据写入到输出通道中,相当于从缓冲区中读取数据,在此之前,必须调用flip()方法,该方法将会完成两件事情:
1. 把limit设置为当前的position值
2. 把position设置为0
由于position被设置为0,所以可以保证在下一步输出时读取到的是缓冲区中的第一个字节,而limit被设置为当前的position,可以保证读取的数据正好是之前写入到缓冲区中的数据,如下图所示:
现在调用get()方法从缓冲区中读取数据写入到输出通道,这会导致position的增加而limit保持不变,但position不会超过limit的值,所以在读取我们之前写入到缓冲区中的4个自己之后,position和limit的值都为4,如下图所示:
在从缓冲区中读取数据完毕后,limit的值仍然保持在我们调用flip()方法时的值,调用clear()方法能够把所有的状态变化设置为初始化时的值,如下图所示:
简单示例:
import java.nio.IntBuffer; public class IntBufferTest { public static void main(String[] args) { // 分配新的int缓冲区,参数为缓冲区容量 // 新缓冲区的当前位置将为零,其limit将为其capacity。它将具有一个底层实现数组,其数组偏移量将为零。 IntBuffer buffer = IntBuffer.allocate(10); System.out.print("初始化: "); System.out.print("position: " + buffer.position() + ", "); System.out.print("limit: " + buffer.limit() + ", "); System.out.println("capacity: " + buffer.capacity()); for (int i = 0; i < 5; ++i) { // 将给定整数写入此缓冲区的当前位置,当前位置递增 buffer.put(i); } System.out.print("put值后: "); System.out.print("position: " + buffer.position() + ", "); System.out.print("limit: " + buffer.limit() + ", "); System.out.println("capacity: " + buffer.capacity()); // 重设此缓冲区,将limit为当前位置position,然后将当前位置position设置为0 buffer.flip(); System.out.print("flip()后: "); System.out.print("position: " + buffer.position() + ", "); System.out.print("limit: " + buffer.limit() + ", "); System.out.println("capacity: " + buffer.capacity()); // 查看在当前位置和限制位置之间是否有元素 while (buffer.hasRemaining()) { // 读取此缓冲区当前位置的整数,然后当前位置递增 int j = buffer.get(); System.out.print(j + " "); } System.out.println(); buffer.clear(); System.out.print("clear()后: "); System.out.print("position: " + buffer.position() + ", "); System.out.print("limit: " + buffer.limit() + ", "); System.out.println("capacity: " + buffer.capacity()); } }运行结果:
初始化: position: 0, limit: 10, capacity: 10 put值后: position: 5, limit: 10, capacity: 10 flip()后: position: 0, limit: 5, capacity: 10 0 1 2 3 4 clear()后: position: 0, limit: 10, capacity: 10
通道Channel
通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口。它们之间的继承关系如下图所示:
使用NIO读取数据
在前面我们说过,任何时候读取数据,都不是直接从通道读取,而是从通道读取到缓冲区。所以使用NIO读取数据可以分为下面三个步骤:
1. 从FileInputStream获取Channel
2. 创建Buffer
3. 将数据从Channel读取到Buffer中
下面是一个简单的使用NIO从文件中读取数据的例子:
static void read() throws Exception { FileInputStream fin = new FileInputStream("build.gradle"); FileChannel fc = fin.getChannel(); // 获取通道 ByteBuffer buffer = ByteBuffer.allocate(1024);// 创建缓冲区 for (; fc.read(buffer) > 0/* 读取数据到缓冲区 */;) { buffer.flip(); while (buffer.remaining() > 0) { byte b = buffer.get(); System.out.print(((char) b)); } buffer.clear(); } fin.close(); }使用NIO写入数据
使用NIO写入数据与读取数据的过程类似,同样数据不是直接写入通道,而是写入缓冲区,可以分为下面三个步骤:
1. 从FileInputStream获取Channel
2. 创建Buffer
3. 将数据从Channel写入到Buffer中
下面是一个简单的使用NIO向文件中写入数据的例子:
static void write() throws Exception { FileOutputStream fout = new FileOutputStream("c:\\test.txt"); FileChannel fc = fout.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); for (int i = 0; i < 10; ++i) { buffer.put((byte) i); } buffer.flip(); fc.write(buffer); fout.close(); }
相关文章推荐
- 使用dom4j 读写xml文件
- Java中的异常体系
- Struts+Hibernate常用工具类
- java8新特性
- JAVA中的String类--字符链接运算符“+”
- spring 第一篇(1-1):让java开发变得更简单(下)转
- 003_eclipse安装教程
- Struts2异常处理配置
- 总结:Ubuntu上安装JDK和Eclipse
- Java_变量、常量、数值转换、字符串、输入输出
- java 正则(4) 不常用 / 贪婪匹配 / 非捕获组 / 向前引用 / flag 简写
- java图片处理--第三方JAR
- eclipse修改js文件cpu爆满 卡死解决方法
- Struts+Hibernate 配置文件作用
- java服务端通信时一定要统一好jdk版本号
- java图片裁切工具类
- spring mvc的配置文件解析
- JavaWeb学习总结(五十三)——Web应用中使用JavaMail发送邮件
- JavaWeb学习总结(五十二)——使用JavaMail创建邮件和发送邮件
- JavaWeb学习总结(五十一)——邮件的发送与接收原理