一、概述

  1.什么是NIO

  NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。

  在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO

  更多介绍与全面概述,请参见:http://www.iteye.com/magazines/132-Java-NIO#579

  2.NIO与IO的主要区别

  

    // 原先的IO是面向流的,类似水流,是单向的;现在的NIO是面向缓冲的双向的,类似铁路,提供一个运输的通道,实际运输的是火车(缓冲区)

    简而言之,通道(channel)负责传输(打开通往文件的通道),缓冲区(Buffer)负责存储

    其它特性将会在后续陆续介绍

  3.NIO的主要内容

二、通道(channel)与缓冲区(buffer)

  1.缓冲区的数据存取 

  初始化分配缓冲区:——通过两个静态方法

  

  数据存取:——主要通过put和get方法

  

  要操作缓冲区,必须理解其中四个重要属性——位于父类Buffer中:

  

  capacity:容量——缓冲区最大存储数据容量,一旦声明不能改变(底层数组的限制)

  limit:界限——缓冲区中可操作数据的大小(limit后的数据不能操作)

  position:位置——缓冲区中正在操作数据的位置

  mark:标记——记住当前position的位置,可以通过reset()恢复到position位置

    // 注意这三个属性都是 Int 类型,代表的是位置(可以理解为类似数组下标)

    图解如下:

    

  测试以上几个属性如下:

  初始状态:

 @Test
public void test1() {
// 分配一个指定大小的字节缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
}

  结果:

  

  使用put存数据:

  @Test
public void test1() {
// 分配一个指定大小的字节缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
String str = "i love the world!";
buf.put(str.getBytes());
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
}

  

  读数据模式:

 @Test
public void test1() {
// 分配一个指定大小的字节缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
String str = "i love the world!";
// 存数据模式
buf.put(str.getBytes());
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// 读数据模式——flip()
buf.flip();
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); }

  结果:

  

  使用get读取数据:

 @Test
public void test1() {
// 分配一个指定大小的字节缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
String str = "i love the world!";
// 存数据模式put()
buf.put(str.getBytes());
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// 读数据模式——flip()
buf.flip();
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// 利用get()读取缓冲区数据
byte[] dst = new byte[buf.limit()];
buf.get(dst);
System.out.println(new String(dst, 0, dst.length));
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
}

  结果:

  

  //读数据的时候游标position也是会移动过去的,就像读模式游标自动到首位

  使用rewind()重读数据(将get读取时导致的position的位置偏移改回来,使position回到初始位置0)

 @Test
public void test1() {
// 分配一个指定大小的字节缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
String str = "i love the world!";
// 存数据模式put()
buf.put(str.getBytes());
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// 读数据模式——flip()
buf.flip();
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// 利用get()读取缓冲区数据
byte[] dst = new byte[buf.limit()];
buf.get(dst);
System.out.println(new String(dst, 0, dst.length));
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// rewind()进行重读
buf.rewind();
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
}

  结果:

  

  使用clear()清空数据缓冲区

  @Test
public void test1() {
// 分配一个指定大小的字节缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
String str = "i love the world!";
// 存数据模式put()
buf.put(str.getBytes());
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// 读数据模式——flip()
buf.flip();
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// 利用get()读取缓冲区数据
byte[] dst = new byte[buf.limit()];
buf.get(dst);
System.out.println(new String(dst, 0, dst.length));
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// rewind()进行重读
buf.rewind();
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// clear()进行缓冲区清空
buf.clear();
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); }

  结果:(位置清零,限制置为容量,回到最初状态,但并不是真正清空缓冲区,只是相应属性被重置,缓冲区数据处于“被遗忘”状态)

  

  使用reset()重置到mark()标记的位置:

   @Test
public void test1() {
// 分配一个指定大小的字节缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
String str = "i love the world!";
// 存数据模式put()
buf.put(str.getBytes());
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// 读数据模式——flip()
buf.flip();
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// 利用get()读取缓冲区数据
byte[] dst = new byte[buf.limit()];
buf.get(dst, 0, 6);
System.out.println(new String(dst, 0, 6));
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
// 通过mark()标记position位置
buf.mark();
// 继续读取,改变position位置
buf.get(dst, 6,4);
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity());
System.out.println(new String(dst, 6, 4));
// 使用reset()恢复position到mark位置
buf.reset();
System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); }

  结果:

  

  //其它例如查看缓冲区可操作数据的方法 hasRemaining()等参见API

  

  2.直接缓冲区与非直接缓冲区

  概念:

  非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
  直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率

  关于这方面的详细介绍,请参见:http://www.cnblogs.com/androidsuperman/p/7083049.html

    API中也有相关的判断直接缓冲区的方法:isDirect()

  3.通道(channel)

  概念:

  通道(Channel):由 java.nio.channels 包定义 的。Channel 表示 IO 源与目标打开的连接。 Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。

  通道是访问IO服务的导管,负责缓冲区数据的传输,配合缓冲区传输数据。通过通道,我们可以以最小的开销来访问操作系统的I/O服务

  通道相关的介绍,请参见 风一样的码农 的随笔:http://www.cnblogs.com/chenpi/p/6481271.html

  分类:主要是红箭头所指处的实现类

  

  获取通道:——打开通道,打开一条向哪个文件的通道

     getChannel()

    静态方法 open()——1.7后的NIO.2

    Files工具类的 newByteChannel()——1.7后的NIO.2

  使用通道:

    使用方式一进行文件复制:  

 @Test
public void test1() {
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel fisChannel = null;
FileChannel fosChannel = null;
try {
fis = new FileInputStream(new File("D:\\test\\1.jpg"));
fos = new FileOutputStream(new File("D:\\test\\2.jpg")); // 获取通道
fisChannel = fis.getChannel();
fosChannel = fos.getChannel(); // 分配非直接缓冲区进行数据存取
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 与IO流类似的读取思路
while ((fisChannel.read(byteBuffer)) != -1) {
// 将读到的数据进行存储,一定要切换为读模式!
byteBuffer.flip();// 切换读模式
fosChannel.write(byteBuffer);
byteBuffer.clear();// 清空缓冲区
}
System.out.println("复制完成!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4个平行的if即使某个出现异常,后续的也会关闭。而且后续1.7后获取通道将会简化
if (fosChannel != null) {
try {
// 显示关闭流
fosChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fisChannel != null) {
try {
// 显示关闭流
fisChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
// 显示关闭流
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
// 显示关闭流
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }

  // 也不需要缓冲流包装之类了

    使用方式二通过内存映射进行文件复制:

 @Test
public void test2() {
FileChannel inChnnel = null;
FileChannel outChnnel = null;
try {
// NIO.2的方式获取读通道
inChnnel = FileChannel.open(Paths.get("D:\\test\\2.jpg"), StandardOpenOption.READ);
// 由于outMappedBuf为读写模式,故这里给outChannel加读写两个模式(CREATE_NEW为创建新文件)
outChnnel = FileChannel.open(Paths.get("D:\\test\\3.jpg"), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE,StandardOpenOption.READ);
// 使用内存映射创建直接缓冲区(只有byteBuffer支持),与allocateDirect()原理一致
MappedByteBuffer inMappedBuf = inChnnel.map(FileChannel.MapMode.READ_ONLY, 0, inChnnel.size());
MappedByteBuffer outMappedBuf = outChnnel.map(FileChannel.MapMode.READ_WRITE, 0, inChnnel.size());
// 现在往缓冲区中放数据就直接放到文件中了,无需通过通道读写
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outChnnel != null) {
try {
outChnnel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inChnnel != null) {
try {
inChnnel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }

  关于其中得MappedByteBuffer,请参见http://blog.csdn.net/z69183787/article/details/53695590

    事实上,NIO提供了通道之间传输数据得方式:

    

   @Test
public void test3() {
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
// NIO.2的方式获取读通道
inChannel = FileChannel.open(Paths.get("D:\\test\\3.jpg"), StandardOpenOption.READ);
// 由于outMappedBuf为读写模式,故这里给outChannel加读写两个模式
outChannel = FileChannel.open(Paths.get("D:\\test\\4.jpg"), StandardOpenOption.CREATE, StandardOpenOption.WRITE,StandardOpenOption.READ);
// 通道之间传送数据
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

   FileChannel常用方法:

  

   4.分散和聚集

   分散读取(Scattering Reads):

    将通道中的数据分散到各个缓冲区中

  @Test
public void test4() {
RandomAccessFile raf = null;
FileChannel rafChannel = null;
try {
raf = new RandomAccessFile("D:\\test\\hello.txt", "rw");
rafChannel = raf.getChannel();
// 分配缓冲区
ByteBuffer byteBuffer1 = ByteBuffer.allocate(400);
ByteBuffer byteBuffer2 = ByteBuffer.allocate(201);
ByteBuffer byteBuffer3 = ByteBuffer.allocate(800);
ByteBuffer[] bufs = {byteBuffer1, byteBuffer2, byteBuffer3};
// 分散读取
rafChannel.read(bufs);
// 切换读模式,读取缓冲区数据
for (ByteBuffer buf : bufs) {
buf.flip();
System.out.println(new String(buf.array(), 0, buf.limit()));
System.out.println("==================================");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (rafChannel != null) {
try {
rafChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

  聚集写入(Gathering Writes):

    将多个缓冲区中的数据聚集到通道中

  示例与分散类似,也是操作缓冲区数组

  5.字符集(Charset)

  编码:编成字节码(数组)——将字符串转换成字节数组

  解码:解释成看的懂的字符串——将字节数组解成字符串

  查看所有可用的字符集:

@Test
public void test5() {
// 所有可用的字符集
Map<String, Charset> map = Charset.availableCharsets();
for (Map.Entry<String, Charset> entry : map.entrySet()) {
System.out.println("key:"+entry.getKey()+" value:"+entry.getValue());
}
}

  编码:

  @Test
public void test6() {
// 获取字符集的字符集对象
Charset cs1 = Charset.forName("GBK");
// 获取编码器与解码器
CharsetEncoder encoder = cs1.newEncoder();
CharsetDecoder decoder = cs1.newDecoder();
// 进行编码与解码(其实也就是CharBuffer与ByteBuffer之间的转换)
CharBuffer charBuffer = CharBuffer.allocate(1024);
charBuffer.put("金陵岂是池中物,一遇风云便化龙。");
// 切换读模式,开始编码
charBuffer.flip();
ByteBuffer byteBuffer = null;
try {
byteBuffer = encoder.encode(charBuffer);
} catch (IOException e) {
e.printStackTrace();
}
for (int i = 0; i < 32; i++){
System.out.println(byteBuffer.get());
}
}

  结果是字节数组的形式(数值):

  

  解码查看:

    @Test
public void test6() {
// 获取字符集的字符集对象
Charset cs1 = Charset.forName("GBK");
// 获取编码器与解码器
CharsetEncoder encoder = cs1.newEncoder();
CharsetDecoder decoder = cs1.newDecoder();
// 进行编码与解码(其实也就是CharBuffer与ByteBuffer之间的转换)
CharBuffer charBuffer = CharBuffer.allocate(1024);
charBuffer.put("金陵岂是池中物,一遇风云便化龙。");
// 切换读模式,开始编码
charBuffer.flip();
ByteBuffer byteBuffer = null;
try {
byteBuffer = encoder.encode(charBuffer);
CharBuffer charBuffer1 = decoder.decode(byteBuffer);
System.out.println(charBuffer1.toString());
} catch (IOException e) {
e.printStackTrace();
}
// for (int i = 0; i < 32; i++){
// System.out.println(byteBuffer.get());
// }
}

  

Java基础——NIO(一)通道与缓冲区的更多相关文章

  1. Java基础-虚拟内存之映射字节缓冲区(MappedByteBuffer)

    Java基础-虚拟内存之映射字节缓冲区(MappedByteBuffer) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.映射字节缓冲区 1>.什么是虚拟内存 答:虚拟内 ...

  2. NIO的通道和缓冲区

    概述 通道和缓冲区是 NIO 中的核心对象,几乎在每一个 I/O 操作中都要使用它们. 通道是对原I/O包中的流的模拟.到任何目的地(或来自任何地方)的所有数据都必须通过一个Channel对象.一个B ...

  3. Java基础--NIO

    NIO库在JDK1.4中引入,它以标准Java代码提供了高速的,面向块的IO,弥补了之前同步IO的不足. 缓冲区Buffer Buffers是一个对象,包含了一些要写入或读出的数据.在面向流的IO模型 ...

  4. Java基础——NIO(二)非阻塞式网络通信与NIO2新增类库

    一.NIO非阻塞式网络通信 1.阻塞与非阻塞的概念  传统的 IO 流都是阻塞式的.也就是说,当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在 ...

  5. Java NIO Channel通道

    原文链接:http://tutorials.jenkov.com/java-nio/channels.html Java NIO Channel通道和流非常相似,主要有以下几点区别: 通道可以读也可以 ...

  6. java基础-网络编程(Socket)技术选型入门之NIO技术

    java基础-网络编程(Socket)技术选型入门之NIO技术 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.传统的网络编程 1>.编写socket通信的MyServer ...

  7. JAVA基础(10)——IO、NIO

    转载:http://blog.csdn.net/weitry/article/details/52964948 JAVA基础系列规划: JAVA基础(1)——基本概念 JAVA基础(2)——数据类型 ...

  8. Java NIO(二)缓冲区

    概念 缓冲区:一个用于特定基本数据类型的容器,由java.nio包定义的所有缓冲区都是Buffer抽象类的子类.其作用于与NIO的通道进行交互,数据从通道读入缓冲区,数据从缓冲区写入通道 Buffer ...

  9. Java基础之NIO

    NIO简介: Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同 ...

随机推荐

  1. JavaScript 变量声明提升

    JavaScript 变量声明提升 一.变量提升的部分只是变量的声明,赋值语句和可执行的代码逻辑还保持在原地不动 二.在基本的语句(或者说代码块)中(比如:if语句.for语句.while语句.swi ...

  2. list 去重并保持原来排序

    public <T> List<T> removeDuplicateKeepOrder(List<T> list){ /* Set set = new HashSe ...

  3. centos6.4 minimal 安装kvm

    操作系统是网易源下载的centos 64位的minimal安装包,很多工具都没有,像gcc make wget which where 等统统没有,好在有yum 这里为了简单起见直接用yum安装kvm ...

  4. Mac OS Yosemite(10.10.3)系统下环境配置

    /etc/bash_profile #Java export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.7.0_65.jdk/Contents/ ...

  5. jQueryeasyUI+Hibernate+struts2实现商城后台管理之复合类别

    一.在Category.java中添加父类别和子类别两个变量,并生成get/set方法

  6. Template-Driven Forms 模板驱动式表单

    Angular 4.x 中有两种表单: Template-Driven Forms - 模板驱动式表单 (类似于 AngularJS 1.x 中的表单 ) Reactive Forms - 响应式表单 ...

  7. jq局部打印插件jQuery.print.js(兼容IE8)

    /* @license * jQuery.print, version 1.5.1 * (c) Sathvik Ponangi, Doers' Guild * Licence: CC-By (http ...

  8. 最大传输单元MTU

    http://baike.baidu.com/link?url=mU41JFjZzOb3R5crQFCNdocT5ovAswcoIqL2A4U6O5Re_U0-HIYndHG0vSKwc6HbptvH ...

  9. 日志采集框架Flume

    前言 在一个完整的大数据处理系统中,除了hdfs+mapreduce+hive组成分析系统的核心之外,还需要数据采集.结果数据导出.任务调度等不可或缺的辅助系统,而这些辅助工具在hadoop生态体系中 ...

  10. 修改eclipse中文件打开默认方式

    Window--->prefrence---->Editors----->FileAssociation 选择文件后缀,如果没有就添加,然后在上添加,删除,设置默认打开方式.